Skip to content
Spring Boot sb reactive 4 min read

Reactive Programming

Reactive programming is an asynchronous, non-blocking style of writing code that handles streams of data with backpressure. In Spring Boot it is delivered through Spring WebFlux and Project Reactor, an alternative to the traditional blocking Spring MVC stack. This section explains the model and shows when it earns its keep — and when it just adds complexity.

What “non-blocking” actually means

In the classic thread-per-request model used by Spring MVC, every incoming HTTP request occupies one thread for its entire lifetime. When that thread calls a database or another service, it blocks — it sits idle waiting for I/O while still consuming memory (a stack, typically ~1 MB). Under high concurrency you run out of threads long before you run out of CPU.

The reactive model flips this. A small pool of event loop threads (often one per CPU core) never block. When a handler issues an I/O call, it registers a callback and immediately moves on to other work. The result is delivered later, when the data is ready. A handful of threads can juggle tens of thousands of concurrent connections.

Thread-per-request (MVC):     Event loop (WebFlux):
  req → [Thread 1] ───wait──    req ─┐
  req → [Thread 2] ───wait──    req ─┼→ [event loop] ⇄ non-blocking I/O
  req → [Thread 3] ───wait──    req ─┘   (handful of threads)
  (1 thread blocked per call)   (no thread ever waits)

The Reactive Streams specification

Reactive Streams is a tiny standard (now part of the JDK as java.util.concurrent.Flow) that defines how asynchronous stream processing with non-blocking backpressure should work. It is built on four interfaces.

InterfaceRole
Publisher<T>Produces a (possibly infinite) sequence of items
Subscriber<T>Consumes items, errors, and a completion signal
SubscriptionThe link between them; the subscriber uses it to request items
Processor<T,R>Both a subscriber and a publisher (a stage in a pipeline)

The crucial idea is backpressure. A subscriber calls subscription.request(n) to ask for only as many items as it can handle. A fast publisher can never overwhelm a slow consumer, because the consumer controls the flow. This is the safety valve that makes streaming over the network reliable.

 Subscriber ── request(10) ──▶ Publisher
 Subscriber ◀── onNext × 10 ── Publisher
 Subscriber ── request(10) ──▶ Publisher   (asks for more when ready)
 Subscriber ◀──── onComplete ── Publisher

Note: Project Reactor’s Mono and Flux are concrete Publisher implementations. You almost never implement the raw interfaces yourself — see Mono & Flux.

When reactive helps

Reactive shines when threads would otherwise spend their lives waiting:

  • High concurrency, I/O-bound workloads — API gateways, BFF layers, and services that mostly fan out to other services or databases.
  • Streaming — Server-Sent Events, large file transfers, or live feeds where you push data as it arrives rather than buffering it all in memory.
  • Many slow clients — mobile or IoT devices on poor networks where connections stay open a long time.

In these scenarios a reactive service handles far more concurrent requests on the same hardware, with smaller, more stable memory usage.

When reactive hurts

Reactive is not a free lunch. Be honest about the costs:

  • Cognitive overhead — the whole pipeline becomes a chain of operators. Stack traces are harder to read and step-debugging is awkward.
  • Blocking drivers poison the pool — one accidental blocking call (JDBC, RestTemplate, file I/O) on an event loop thread can stall thousands of requests. JPA/Hibernate is fundamentally blocking and cannot be used reactively; you need R2DBC instead.
  • Smaller ecosystem — fewer libraries have reactive equivalents, and team familiarity is usually lower.
  • CPU-bound work gains nothing — if your bottleneck is computation, not waiting, the event loop just gets in the way.

Tip: Since Java 21, virtual threads give much of the scalability of non-blocking I/O while keeping the simple, readable blocking programming model. For many applications they are the better trade-off. See the honest comparison in WebFlux vs Spring MVC.

A first taste

A reactive endpoint returns a Publisher (Mono or Flux) instead of a value. Spring subscribes to it and writes the result to the response as it arrives — no thread is parked while the data is fetched.

@RestController
class GreetingController {

    @GetMapping("/hello/{name}")
    Mono<String> hello(@PathVariable String name) {
        return Mono.just("Hello, " + name + "!");   // 0..1 result, no blocking
    }
}

Compare this with a blocking MVC handler that returns String directly. The shape is similar, but the reactive version composes asynchronously and never holds a thread hostage.

In This Section

Last updated June 13, 2026
Was this helpful?