
Introduction
In recent years, the demands on modern web applications have grown rapidly. Users now expect real-time updates, fast response times, and smooth experiences—even with thousands of concurrent users. Traditional Java web stacks like Spring MVC, which use a thread-per-request model, often struggle to scale under such load.
That’s where reactive programming comes in.
It’s a paradigm designed to handle asynchronous data streams in a non-blocking, event-driven way—ideal for building high-concurrency, low-latency systems that are increasingly common in today’s cloud-native world.
In Java, this model is supported by Project Reactor and Spring WebFlux, which let developers build responsive APIs without the cost of thread-per-client overhead.
In this post, I will explore the core ideas of reactive programming in Java, how WebFlux enables it, and walk through a real use case you can apply right away—without over engineering your app.
Key concepts of reactive programming
Reactive programming is all about changing the way we think—from dealing with values that are available right away to handling streams of data that arrive over time.
The reactive approach is based on four key principles, commonly referred to as the Reactive Manifesto:
- Responsive – The system responds in a timely manner.
- Resilient – The system stays responsive in the face of failure.
- Elastic – The system stays responsive under varying load.
- Message-driven – The system uses asynchronous message passing.
In Java, reactive programming is formalized through the Reactive streams specification—a standard for asynchronous stream processing with non-blocking back pressure. This is crucial for systems under load, where unbounded data streams can overwhelm slower consumers.
Project reactor: Flux and Mono
The most popular reactive library in the Java ecosystem is Project Reactor, and it’s the foundation of Spring WebFlux.
It introduces two main data types:
Mono<T>
: Represents a single asynchronous value (like Optional or a Future)Flux<T>
: Represents a sequence of 0…N asynchronous values (like a stream)

Here’s a quick example:
Mono monoName = Mono.just("Alice");
Flux fluxNames = Flux.just("Alice", "Bob", "Charlie").map(String::toUpperCase);
Both Mono and Flux are lazy—they don’t do anything until you subscribe to them. This enables efficient chaining of operations such as mapping, filtering, and error handling without actually blocking a thread.
Back pressure and Flow control
Backpressure is a core concept that makes reactive systems resilient under load. If a downstream consumer (e.g., a UI or database) can’t keep up with the rate of incoming data, it can signal the producer to slow down or buffer intelligently—without dropping the whole system.
Reactor handles this behind the scenes with non-blocking I/O, making it easier to build applications that scale naturally with demand.
Spring WebFlux overview
What is Spring WebFlux?
Spring WebFlux is Spring’s reactive web framework introduced in Spring 5. It was designed to address the limitations of the traditional servlet-based model (Spring MVC), which handles each request using a dedicated thread. While that works well for low to moderate traffic, it doesn’t scale efficiently for applications with high concurrency or real-time data streaming requirements.
WebFlux takes a fundamentally different approach: it uses a non-blocking event loop model (based on Netty by default) and leverages Project Reactor to manage asynchronous streams of data efficiently. This means you can serve thousands of concurrent requests without creating thousands of threads.
Key Differences from Spring MVC:
Feature | Spring MVC | Spring WebFlux |
Programming Model | Imperative (blocking) | Reactive (non-blocking) |
Thread Handling | One thread per request | Event-loop with few threads |
Return Types | Synchronous types (e.g. List) | Mono, Flux from Reactor |
Default Server | Servlet containers (Tomcat) | Netty (or optionally Tomcat) |
Performance | Good under low load | Scales better under high load |
Minimal Example: A Reactive REST Endpoint
@GetMapping("/hello")
public Mono sayHello()
{
return Mono.just("Hello, reactive world!");
}
Even for a simple endpoint like this, WebFlux allows it to be non-blocking from end to end—ideal for chaining async service calls or database access.
Real Use Case: Streaming Data API with WebFlux
Let’s put the theory to the test with a practical example. Imagine you’re creating a dashboard that shows livestock prices updating in real time. With traditional REST APIs, this often means implementing polling logic on the client—inefficient, wasteful, and far from “real-time.”
With Spring WebFlux, you can stream updates directly to the client using Server-Sent Events (SSE). This allows the server to push data as it becomes available, keeping the connection open without overloading server threads.
The Goal
Build a REST endpoint that emits new stock prices every second, simulating a live feed.
Domain Model
public record StockPrice(String symbol, double price, LocalDateTime timestamp) {}
Service Layer
@Service
public class StockService {
public Flux<StockPrice> getLivePrices(String symbol) {
return Flux.interval(Duration.ofSeconds(1))
.map(i -> new StockPrice(symbol, generateRandomPrice(),LocalDateTime.now()));
}
private double generateRandomPrice() {
return 100 + Math.random() * 50; // Simulate price fluctuation
}
}
Here, Flux.interval()
emits a signal every second, and we map it to a simulated stock price.
Controller Layer
RestController
@RequestMapping("/stocks")
public class StockController {
private final StockService stockService;
public StockController(StockService stockService) {
this.stockService = stockService;
}
@GetMapping(value = "/{symbol}/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<StockPrice> streamStockPrices(@PathVariable String symbol) {
return stockService.getLivePrices(symbol);
}
}
Using TEXT_EVENT_STREAM_VALUE
, we tell Spring to stream data as SSE (Server-Sent Events). The client will receive one update per second without making repeated HTTP requests.
Client Example
const source = new EventSource("/stocks/AAPL/stream");
source.onmessage = (event) => {
const price = JSON.parse(event.data);
console.log("New price:", price);
};
This approach is:
- Scalable (fewer threads)
- Efficient (no polling)
- Real-time (instant updates)
When to use (and not use) WebFlux
Spring WebFlux is a powerful tool—but it’s not a silver bullet. Knowing when to apply it (and when to stick with traditional Spring MVC) is key to building maintainable, high-performance applications.
When to use WebFlux
WebFlux shines in scenarios where scalability and concurrency are critical. These include:
- Real-time dashboards: Live updates for stock prices, analytics, or system monitoring.
- IoT platforms: Handling data from thousands of connected devices streaming sensor data.
- Chat applications or notification systems: Where low latency and persistent connections are essential.
- API gateways or aggregators: When making multiple non-blocking service/database calls concurrently.
Because WebFlux is non-blocking, it can handle significantly more concurrent users with fewer resources—ideal for cloud-native environments or microservices under heavy load.
When not to use WebFlux
Reactive programming has a learning curve and comes with trade-offs. Avoid WebFlux if:
- Your application is CPU-bound, not I/O-bound. Reactive models don’t help with raw computation.
- You rely heavily on blocking APIs, like JDBC or legacy SOAP services. Mixing blocking calls in a reactive flow defeats the purpose and can cause performance bottlenecks.
- Your team is unfamiliar with reactive concepts. Debugging and tracing errors through a reactive pipeline is harder than with imperative code.
- The application is simple and doesn’t require high concurrency. Spring MVC is easier to read and maintain for straightforward CRUD apps.
Example
Pitfall: Blocking in Reactive Code
// ❌ Don't do this in WebFlux
Mono data = Mono.fromCallable(() -> blockingService.loadData())
.subscribeOn(Schedulers.boundedElastic());
While this might “work,” it’s a workaround that adds complexity and risk. If you need blocking APIs, you’re better off using Spring MVC or isolating those components in separate threads/services.
Conclusion
Reactive programming with Java may seem like a big shift at first—but for the right kinds of applications, it’s a game-changer. By embracing Spring WebFlux, developers can build highly concurrent, scalable, and responsive web applications. Whether you’re streaming live data, supporting thousands of users, or composing multiple async service calls, WebFlux enables this with fewer resources and better performance.
That said, like any tool, it’s best used in the right context. For simpler applications that don’t require real-time responsiveness or involve blocking operations, traditional Spring MVC might still be the better fit.
If you’re just starting out, consider applying WebFlux to one part of your system—like a streaming endpoint or a low-risk API—to gain hands-on experience without overhauling your entire architecture. At zen8Labs, we specialize in designing and delivering high-performance, modern software solutions tailored to your business needs. Whether you’re exploring reactive systems or scaling existing infrastructure, our team can help you navigate the complexity with the right technology and strategy.
Luan Dinh, Software Engineer