Open-Source Wikis

/

Spring Framework

/

Features

/

Reactive stack

spring-projects/spring-framework

Reactive stack

Active contributors: rstoyanchev, Sébastien Deleuze, Juergen Hoeller, Brian Clozel

The reactive stack is the non-blocking complement to Spring's traditional Servlet-based web framework. It centers on Reactor types (Mono, Flux) and operates end-to-end without thread-per-request semantics. This page walks through what's reactive in the framework and how the pieces work together.

What's reactive

Layer Module Reactive entry point
Web server spring-webflux DispatcherHandler, WebHandler, WebFilter
HTTP client spring-webflux (WebClient) WebClient
WebSocket spring-webflux / spring-websocket WebSocketHandler
Database spring-r2dbc DatabaseClient
Messaging spring-messaging ReactiveMessageHandler, RSocket
Transactions spring-tx ReactiveTransactionManager, TransactionalOperator
Validation spring-context Validator is sync; method-level @Validated works on reactive returns via the ReactiveAdapterRegistry

Reactor as the lingua franca

Spring's reactive APIs return Reactor's Mono<T> (0..1 value) and Flux<T> (0..N values). Other reactive libraries (RxJava 3, SmallRye Mutiny, Kotlin coroutines) are supported via adapters from org.springframework.core.ReactiveAdapterRegistry (in spring-core). Anywhere Spring accepts a Mono/Flux, it accepts the equivalent in those libraries through the adapter.

End-to-end reactive request

sequenceDiagram
    participant Client
    participant Server as Reactor Netty
    participant DH as DispatcherHandler
    participant Filter as WebFilters
    participant Ctrl as @RestController
    participant DC as DatabaseClient
    participant DB as Database
    Client->>Server: HTTP request
    Server->>DH: ServerWebExchange
    DH->>Filter: chain.filter(exchange)
    Filter->>DH: passthrough
    DH->>Ctrl: handler invocation (reactive arg resolution)
    Ctrl->>DC: client.sql(…).bind(…).all()
    DC->>DB: SQL via R2DBC driver (non-blocking)
    DB-->>DC: rows (streaming)
    DC-->>Ctrl: Flux<Row>
    Ctrl-->>DH: Mono<ResponseEntity<Flux<User>>>
    DH->>Server: subscribe and write
    Server-->>Client: HTTP response (streaming, backpressure-aware)

No thread blocks at any point. The transport (Reactor Netty) signals demand backwards through the chain; the database produces rows on demand; the framework writes them out as they're available.

Why reactive?

Reactive is not automatically faster. It excels at:

  • High-concurrency I/O-bound workloads — chat servers, gateway proxies, fan-out APIs
  • Streaming responses — server-sent events, large payloads, slow clients
  • Resource-constrained environments — fewer threads needed to handle many in-flight requests

It is not the right choice for:

  • Simple CRUD apps with low concurrency
  • Apps with heavy CPU-bound work (reactive doesn't help)
  • Apps that already saturate available threads in MVC

The framework intentionally keeps both stacks first-class so teams pick by fit, not by recency.

WebFlux annotation model

@RestController
public class UserController {

    private final UserRepository repo;

    @GetMapping("/users/{id}")
    public Mono<User> get(@PathVariable Long id) {
        return repo.findById(id);
    }

    @GetMapping("/users")
    public Flux<User> list() {
        return repo.findAll();
    }

    @PostMapping("/users")
    public Mono<User> create(@RequestBody Mono<UserCreate> body) {
        return body.flatMap(repo::save);
    }
}

The annotations are the same as in MVC (@GetMapping, @PathVariable, @RequestBody, @ResponseBody). The difference is the return types and that argument resolution can also produce reactive types (Mono<UserCreate> above) — useful for streaming uploads.

Reactive transactions

@Transactional
public Mono<User> register(UserCreate input) {
    return userRepo.save(new User(...))
        .flatMap(u -> auditRepo.log(...).thenReturn(u));
}

The @Transactional annotation is detected at AOP wiring time. Because the method returns Mono, the framework routes through ReactiveTransactionInterceptor instead of the imperative TransactionInterceptor. The transaction state lives in the Reactor Context, not ThreadLocal.

A ReactiveTransactionManager bean (e.g., R2dbcTransactionManager) must be present.

WebClient

WebClient is the reactive HTTP client. It's the recommended client for both reactive and imperative Spring apps that want non-blocking I/O.

WebClient client = WebClient.create("https://api.example.com");

Mono<User> user = client.get()
    .uri("/users/{id}", id)
    .retrieve()
    .bodyToMono(User.class);

WebClient supports:

  • Streaming bodies (request and response) via Mono/Flux
  • Filters (auth, logging, retry)
  • Pluggable transport (Reactor Netty, Jetty, HttpClient5)
  • Codec configuration via ExchangeStrategies

Reactive WebSockets and RSocket

spring-webflux includes a reactive WebSocket handler model: WebSocketHandler accepts a WebSocketSession and returns Mono<Void> after composing send/receive flows.

RSocket is a fully bidirectional protocol over TCP/WebSocket. Spring's RSocket support lives in spring-messaging (with WebSocket transport via spring-webflux). @MessageMapping methods double as RSocket handlers and STOMP handlers.

Bridging reactive and imperative

Sometimes you need to call blocking code from a reactive context (e.g., a JDBC repository from a WebFlux controller). The standard approach:

Mono.fromCallable(() -> blockingMethod())
    .subscribeOn(Schedulers.boundedElastic())

Schedulers.boundedElastic() is a thread pool sized for blocking I/O (default: 10 × CPU cores). Don't use the default Schedulers.parallel() for blocking — it has very limited threads.

The reverse direction (calling reactive code from imperative) is straightforward:

User u = mono.block();   // blocks the calling thread

…but block() defeats the purpose in a hot path; prefer to make the entire chain reactive.

BlockHound

The framework's tests use BlockHound to detect accidental blocking calls in reactive flows. It's not enabled by default in user apps but is a useful dependency to add when developing reactive code.

Performance gotchas

  • Don't .block() in handlers — defeats the reactive model
  • Avoid ThreadLocal — Reactor flows hop threads; use the Reactor Context instead
  • Backpressure matters — A Flux that emits faster than its consumer can handle needs a buffer or sampling strategy
  • Pin blocking work — Use subscribeOn(Schedulers.boundedElastic()) for blocking I/O

See also

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Reactive stack – Spring Framework wiki | Factory