Reactive Programming
Reactive Programming
A programming paradigm for building non-blocking, event-driven systems that process asynchronous data streams with built-in backpressure, enabling high concurrency without proportional thread overhead.
Core Idea
Reactive programming treats data and events as streams that can be composed, transformed, and consumed asynchronously. Rather than blocking a thread while waiting for a result (database query, HTTP call, file read), reactive code registers callbacks and returns immediately. The thread is released to handle other work until the result arrives.
This model is foundational to Spring-Cloud-Gateway and the BFF-Pattern because a BFF's primary job — calling multiple downstream services and merging results — is inherently an async fan-out problem.
Key Principles
- Responsive — the system responds in a timely manner. Non-blocking I/O keeps threads available so there is always capacity to respond.
- Resilient — failures are modelled as first-class values in the stream (error signals), not exceptions that blow up a thread.
- Elastic — the system scales out/in based on demand; the backpressure mechanism prevents overwhelming slow consumers.
- Message-driven — components communicate via async messages/events, decoupling producers from consumers.
These are the four pillars of the Reactive Manifesto (reactivemanifesto.org).
How It Works
The Reactive Streams Specification
Reactive Streams (reactive-streams.org) is the JVM standard (also part of JDK 9 as java.util.concurrent.Flow). It defines four interfaces:
| Interface | Role |
|---|---|
Publisher<T> | Produces items; has subscribe(Subscriber) method |
Subscriber<T> | Consumes items; receives onNext, onError, onComplete signals |
Subscription | Controls flow; request(n) pulls n items, cancel() stops the stream |
Processor<T,R> | Both Publisher and Subscriber; transforms the stream |
The critical rule: a Publisher only emits items as fast as the Subscriber requests them — this is backpressure.
Reactive vs Imperative: The Thread Model
Imperative (Blocking)
Thread 1: [Request] → [Block: HTTP call A] → [Block: HTTP call B] → [Response]
^ Thread tied up ^ Thread still tied up
- Thread:request ratio is 1:1
- 200 Tomcat threads → 200 max concurrent requests
- Adding more concurrency = adding more threads = more memory
Reactive (Non-blocking)
Thread 1 (event loop): [Request received] → [Register callback for A and B] → [free for next request]
↓
(A and B complete asynchronously)
↓
Thread 1 (event loop): [Callback: A+B done] → [Merge results] → [Response]
- Small fixed thread pool (e.g., 8 I/O threads for an 8-core machine)
- Thousands of concurrent requests handled via callbacks/continuations
- Memory per concurrent request is a few hundred bytes, not 1 MB thread stack
Backpressure in Practice
Without backpressure, a fast producer overwhelms a slow consumer:
Producer: emits 10,000/sec → [buffer overflow] → OOM / data loss
With backpressure, the consumer controls the rate:
Subscriber.request(100) → Publisher emits exactly 100
Subscriber.request(100) → Publisher emits exactly 100 (next batch when ready)
In Project-Reactor / Spring WebFlux, backpressure is automatic when using Flux. When you collectList() or write to an HTTP response, Reactor manages the demand signals internally.
The Event Loop Model
Spring WebFlux (via Reactor Netty) uses the event loop (Netty's NIO model):
┌─────────────────────────────┐
│ Reactor Netty (Netty NIO) │
│ │
Request ──────────► Event Loop Thread 1 │
Request ──────────► Event Loop Thread 2 │──► OS async I/O
Request ──────────► Event Loop Thread 3 │
(thousands) │ ... │
└─────────────────────────────│
│ callbacks
▼
Reactive pipeline executes
(map, flatMap, zip, etc.)
Number of event loop threads ≈ number of CPU cores (configurable). The key rule: never block an event loop thread. Blocking calls must be offloaded to Schedulers.boundedElastic().
Examples
- Non-blocking HTTP call:
WebClient.get().uri("...").retrieve().bodyToMono(Response.class)— returns immediately with aMono<Response>; no thread is blocked. - Parallel aggregation:
Mono.zip(callA(), callB())— subscribes to both simultaneously; event loop handles both I/O callbacks; total time = max(latency A, latency B). - Streaming response:
Flux<ServerSentEvent>piped to an HTTP response enables real-time push without keeping a thread per client.
Common Misconceptions
Reactive is always faster: For simple, low-concurrency CRUD apps, reactive adds complexity without measurable benefit. It shines under high concurrency and I/O-bound workloads.Reactive removes the need for threads: Threads still exist. The event loop runs on threads. The difference is that threads are not blocked waiting for I/O.You can mix blocking and reactive freely: Calling a blocking method inside a reactive chain (on an event loop thread) will stall the event loop and degrade the entire system.: In Spring WebFlux / SCG, the framework subscribes for you. Manually callingsubscribe()is always the right way to consumesubscribe()inside a filter or controller is almost always wrong.
Why It Matters
For the BFF-Pattern specifically:
| Concern | Reactive Solution |
|---|---|
| Fan-out to N services | Mono.zip(...) — parallel, no extra threads |
| Slow downstream | Backpressure + timeout — system stays responsive |
| Downstream failure | onErrorResume — graceful degradation without crashing |
| High client concurrency | Event loop — thousands of clients on 8 threads |
| Token relay / header injection | Filter chain composes reactively end-to-end |
Without reactive programming, a BFF under load either exhausts its thread pool or requires a very large (and expensive) thread pool to sustain concurrency.
Related Concepts
| Concept | Relationship |
|---|---|
| Project-Reactor | The concrete reactive library used in Spring (implements Reactive Streams) |
| Spring-Cloud-Gateway | Runs entirely on the reactive stack |
| BFF-Pattern | Reactive programming enables efficient BFF aggregation |
| Request-Aggregation | Implemented via reactive operators (Mono.zip) |
The Reactive Streams Implementations (JVM)
| Library | Used By |
|---|---|
| Project Reactor | Spring WebFlux, Spring Cloud Gateway |
| RxJava 3 | Android, some microservices frameworks |
| Akka Streams | Akka / Pekko ecosystem |
| Mutiny | Quarkus |
java.util.concurrent.Flow | JDK 9+ (interfaces only, no operators) |
Sources
- reactivemanifesto.org — The Reactive Manifesto
- reactive-streams.org — Reactive Streams specification
- projectreactor.io/docs — Project Reactor reference
- Venkat Subramaniam, "Functional Programming in Java" — chapter on reactive
- P2-Spring-Boot-BFF-Stack (Phase 2 research)