Traditional Java web apps (e.g., Spring MVC, Servlets) are blocking:
Each HTTP request is handled by a dedicated thread → Each thread handles one task at a time.
The thread blocks while waiting for I/O (DB calls, web service calls, etc.).
When load increases, threads pile up → thread exhaustion → poor scalability.
For high-concurrency or real-time systems (chat apps, stock tickers, IoT data streams), blocking models are inefficient.
Threads don't wait — they register callbacks or events.
When the result is ready, it's handled asynchronously.
This allows fewer threads to handle many concurrent users.
It’s non-blocking, asynchronous, and event-driven, allowing systems to handle many concurrent requests using fewer threads.
It's a design paradigm that models data as streams and reactions to changes.
It "reacts" to events (like data updates, messages, requests) instead of pulling or waiting.
Uses Publisher–Subscriber pattern (Reactive Streams specification).
Implementation libraries in Java:
Reactor (used by Spring WebFlux)
RxJava (Reactive Extensions for Java)
It provides the core reactive types:
Mono<T> → Emits 0 or 1 item (like a Promise in JS).
Flux<T> → Emits 0..N items (like an asynchronous stream).
They support:
Non-blocking backpressure (the consumer controls how much data it can handle)
Functional composition (map, flatMap, filter, zip, etc.)
Error handling (onErrorResume, onErrorReturn, etc.)
Example:
Flux.just(1, 2, 3)
.map(i -> i * 2)
.filter(i -> i > 2)
.subscribe(System.out::println);
Publisher: Emits data (source of stream). Mono and Flux are Publishers.
Subscriber: Consumes data emitted by Publisher.
Subscription: Link between Publisher and Subscriber; controls request flow.
Operators: Transform data in the stream (map, flatMap, etc.).
Schedulers: Decide where (which thread) execution happens.
+---------+ +-------------+ +-------------+ +-------------+ +-------------+
| Client | ---> | Dispatcher | ---> | Controller | ---> | Service | ---> | Repository |
| (HTTP) | | Servlet | | (@RestCtrl) | | (Business) | | (JPA/JDBC) |
+---------+ +-------------+ +-------------+ +-------------+ +-------------+
| | | | |
| | | | |
| |<----- waits/block --+ | |
| |<-------------------- waits/block ---------+ |
| |<----------------------------- waits/block ----------------------+
| | |
|<----------------+---------------------------- synchronous HTTP response ----------+
Each request blocks a thread until all layers complete.
Uses Servlet API (thread-per-request model).
Simple but does not scale well under high concurrency.
+---------+ +-------------+ +-------------+ +-------------+ +-------------+
| Client | ---> | WebFlux | ---> | Handler | ---> | Service | ---> | Repository |
| (HTTP) | | Router/ | | (Mono/Flux) | | (Reactive) | |(R2DBC/Mongo)|
+---------+ | Controller | +-------------+ +-------------+ +-------------+
| +-------------+ | | |
| | | | |
| |-- reactive stream -->|-------------------->|-------------------->|
| | | | |
| |<--- backpressure --- |<--------------------|<--------------------|
|<----------------+------- asynchronous stream response -------+---------------------+
Non-blocking I/O powered by Netty (no servlet container threads).
Uses Reactor types (Mono<T>, Flux<T>) and Reactive Streams protocol.
Each layer composes asynchronous flows — no thread waiting.
Event-loop model enables high scalability with fewer threads.
Handles backpressure gracefully.
┌──────────────────────────────────────────────┐
│ Thread Pool (e.g. 200 threads) │
└──────────────────────────────────────────────┘
│ │ │
┌─────────────┘ │ └──────────────┐
▼ ▼ ▼
+---------------+ +---------------+ +---------------+
| HTTP Request 1| | HTTP Request 2| | HTTP Request 3|
+---------------+ +---------------+ +---------------+
│ │ │
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Controller │ │ Controller │ │ Controller │
│ (Blocking call) │ │ (Blocking call) │ │ (Blocking call) │
└──────────────────┘ └──────────────────┘ └──────────────────┘
│ │ │
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Service/Repo │ │ Service/Repo │ │ Service/Repo │
│ (JDBC: blocks) │ │ (JDBC: blocks) │ │ (JDBC: blocks) │
└──────────────────┘ └──────────────────┘ └──────────────────┘
│ │ │
▼ ▼ ▼
[Thread waits for DB] [Thread waits for DB] [Thread waits for DB]
│ │ │
▼ ▼ ▼
Response Response Response
Each request consumes one thread until it finishes.
While waiting for I/O (DB, network, etc.), that thread sits idle.
To handle 10,000 requests, you’d need ~10,000 threads → huge memory + context-switch overhead.
┌──────────────────────────────────────────────┐
│ Small Thread Pool (e.g. 4–8 threads) │
│ Netty Event Loops │
└──────────────────────────────────────────────┘
│ │ │
┌─────────────┘ │ └──────────────┐
▼ ▼ ▼
+---------------+ +---------------+ +---------------+
| HTTP Request 1| | HTTP Request 2| | HTTP Request 3|
+---------------+ +---------------+ +---------------+
│ │ │
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Reactive Handler │ │ Reactive Handler │ │ Reactive Handler │
│ returns Mono/Flux│ │ returns Mono/Flux│ │ returns Mono/Flux│
└──────────────────┘ └──────────────────┘ └──────────────────┘
│ │ │
▼ ▼ ▼
┌────────────────────────────────────────────────────────────────────┐
│ Reactive Pipeline (non-blocking operators, async I/O) │
└────────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
[I/O waits handled asynchronously — threads free to serve others]
│ │ │
▼ ▼ ▼
Response Response Response
Few threads handle thousands of concurrent requests.
No blocking — each I/O operation registers a callback instead of waiting.
Netty’s event loop continuously reacts to ready events (I/O complete, etc.).
Enables massive scalability and resource efficiency.
Provides a non-blocking alternative to Spring MVC.
WebFlux use-case:
High concurrency (many concurrent requests)
The services are I/O-heavy, not CPU-heavy
(1) Reactive runtime (default) — runs on:
Netty (default)
Undertow
Jetty
Tomcat (reactive mode)
(2) Servlet 3.1+ non-blocking I/O runtime
┌───────────────────────────┐
│ Client (HTTP) │
└────────────┬──────────────┘
│
┌────────────▼────────────┐
│ WebFlux Handler │
│ (Router or Controller) │
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ Reactive Streams │
│ (Flux / Mono pipeline)│
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ Reactive I/O runtime │
│ (Netty, Jetty, etc.) │
└─────────────────────────┘
@RestController
public class ReactiveController {
@GetMapping("/hello")
public Mono<String> hello() {
return Mono.just("Hello Reactive World!");
}
@GetMapping("/numbers")
public Flux<Integer> numbers() {
return Flux.range(1, 5).delayElements(Duration.ofMillis(500));
}
}
Mono and Flux automatically produce streaming HTTP responses.
The response is sent as data becomes available (non-blocking).
WebFlux also supports a functional style, avoiding annotations:
@Configuration
public class RouterConfig {
@Bean
public RouterFunction<ServerResponse> routes() {
return RouterFunctions.route()
.GET("/hello", request -> ServerResponse.ok()
.bodyValue("Hello Functional World"))
.GET("/numbers", request -> ServerResponse.ok()
.body(Flux.range(1, 5), Integer.class))
.build();
}
}
Useful for microservices or when you want explicit route configuration.
Produces a Server-Sent Events (SSE) stream — ideal for live dashboards or notifications.
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> stream() {
return Flux.interval(Duration.ofSeconds(1))
.map(seq -> "Message #" + seq);
}
WebFlux shines when the entire stack is reactive, including data layers.
Examples:
Spring Data R2DBC (Reactive SQL)
Reactive MongoDB
Reactive Redis
WebClient (non-blocking HTTP client)
Example with WebClient:
WebClient client = WebClient.create("https://api.example.com");
Mono<String> response = client.get()
.uri("/data")
.retrieve()
.bodyToMono(String.class);
This makes non-blocking outbound HTTP calls, chaining with other reactive streams.