Reactive programming in Java: Real use cases with WebFlux

5 min read
zen8labs Reactive programming in Java: Real use cases with WebFlux

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) 
zen8labs Reactive programming in Java: Real use cases with WebFlux 2

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 

Related posts

Vue.js developers frequently rely on props to allow for communications between components. Here is your chance to learn about $attrs and $listeners in Vue.js
3 min read

Introduction A RESTful API is a web service that follows REST (Representational State Transfer) principles, making it easy to interact with through HTTP methods like GET, POST, PUT, and DELETE. In .NET Core 9, building a RESTful API with Dependency Injection (DI) makes your code modular, testable, and maintainable.  For my article, I will explain

4 min read

What are the differences between threads in Python and threads in other programming languages? What is the GIL? Is it always more efficient to use multithreading and multiprocessing over single-threading and single-processing? In this article, I will discuss the limitations of threads in Python and provide recommendations on when to use multithreading, multiprocessing, or stick

4 min read