WebFlux Reactive Patterns
Use when the complete Spring Boot 3 request path is reactive: WebFlux controllers, Reactor pipelines, R2DBC persistence, streaming, backpressure, or cancellation. For defining and registering declarative outbound API interfaces, use http-interface-clients.
View SKILL.mdUse WebFlux when the complete request path can benefit from non-blocking I/O.
Pipeline rules
- Return
MonoorFlux; do not callsubscribe()in request-handling code. - Compose work with
map,flatMap,concatMap, andzipaccording to ordering requirements. - Keep blocking JDBC, filesystem, and legacy SDK calls out of event-loop threads.
- Isolate unavoidable blocking calls on
boundedElasticat the narrow adapter boundary. - Apply timeouts at remote-call boundaries and preserve cancellation.
Context and errors
- Put request-scoped metadata in Reactor
Context, notThreadLocal. - Translate domain failures centrally without swallowing cancellation or infrastructure errors.
- Use
onErrorResumeonly when a defined fallback exists. - Avoid
onErrorContinue; it makes partial processing hard to reason about.
Persistence and streaming
- Use R2DBC for reactive database access; JDBC makes the pipeline blocking.
- Bound concurrency for fan-out operations.
- Define backpressure and buffering limits for streaming endpoints.
- Avoid collecting an unbounded
Fluxinto memory.
Testing
- Use
StepVerifierfor publisher behavior andWebTestClientfor HTTP contracts. - Test cancellation, timeout, empty results, errors, ordering, and backpressure.
- Enable blocking-call detection in tests when the project supports it.
Examples
- See
examples/good-reactive-service.javaandexamples/bad-reactive-service.java.
Gotchas
- Agent calls
block()in a controller or service - keep the full request path non-blocking. - Agent calls
subscribe()manually - the web runtime owns subscription. - Agent stores tenant or security data in
ThreadLocal- use ReactorContext. - Agent uses unbounded
flatMap- set concurrency according to downstream capacity. - Agent wraps JDBC in
Mono.just- that still blocks the caller thread.
Examples & templates
bad-reactive-service.javaexamples
Source on GitHub
Mono<Order> loadOrder(UUID id) {
Order order = repository.findById(id).block();
return Mono.just(order);
}good-reactive-service.javaexamples
Source on GitHub
import java.time.Duration;
import java.util.UUID;
import reactor.core.publisher.Mono;
final class OrderQueryService {
private final ReactiveOrderRepository repository;
OrderQueryService(ReactiveOrderRepository repository) {
this.repository = repository;
}
Mono<Order> loadOrder(UUID id) {
return repository.findById(id)
.switchIfEmpty(Mono.error(new OrderNotFound(id)))
.timeout(Duration.ofSeconds(2));
}
}
interface ReactiveOrderRepository {
Mono<Order> findById(UUID id);
}
record Order(UUID id) { }
final class OrderNotFound extends RuntimeException {
OrderNotFound(UUID id) {
super("Order not found: " + id);
}
}Verification scope
- Compiled sources
- No
- Runtime checks
- No
Coverage describes checked-in fixtures, not a guarantee for every snippet or project.
Coverage matrix & agent evidenceADD TO YOUR AGENTInstall Boot 3 skills
Install Boot 3 skills
claude plugin marketplace add rrezartprebreza/spring-boot-skills
claude plugin install spring-boot-3-skills@spring-boot-skillscodex plugin marketplace add rrezartprebreza/spring-boot-skills
codex plugin add spring-boot-3-skills@spring-boot-skills