From Monolithic to Microservices: Embrace Spring WebFlux


Monolithic to Microservices

Till the late 2000s, I had witnessed and worked extensively on monolithic applications — the era when a single server hosted the entire system. Some of the popular choices for these central servers were WebLogic, WebSphere, and JBoss. However, that age has long passed. Such architectural choices eventually failed to scale, lacked fault tolerance, and struggled to deliver high availability — paving the way for modern, distributed, and cloud-native architectures.

In the modern world of microservices, cloud-native architectures, and real-time systems, performance, scalability, and responsiveness have become critical. Traditional blocking architectures often struggle when faced with thousands of concurrent users or long-running I/O operations.

This is where Spring WebFlux comes in — a powerful, non-blocking, reactive programming framework that enables developers to build highly scalable and resilient APIs with minimal resource usage.


The Problem with Blocking APIs

What “blocking” means in the context of web applications.In traditional Spring MVC (Servlet-based) applications:

  • Each request is handled by a dedicated thread from a thread pool.
  • When a thread waits for a downstream call (like a database query or REST API), it’s blocked — meaning it can’t handle other requests.
  • The server has a limited thread pool (often 200–500 threads). Once all threads are busy, the system starts rejecting new requests.

For small-scale applications, this works fine. But under heavy load — say thousands of concurrent requests — the application becomes sluggish or even unresponsive.

Example:

@GetMapping("/user/{id}")
public User getUser(@PathVariable String id) {
    // Simulate blocking call (like DB fetch)
    return userRepository.findById(id); // blocking call
}

This method ties up one server thread until the database returns the result.


The Non-Blocking Revolution

In a non-blocking model, threads don’t wait. Instead, they register callbacks or use reactive streams that are notified asynchronously when the result is ready.

This means:

  • Fewer threads can handle many concurrent requests.
  • Resources are used more efficiently.
  • The app remains responsive even under high concurrency.

This concept is inspired by reactive programming, which emphasizes asynchronous data streams and backpressure control.


Spring WebFlux

Spring WebFlux is part of the Spring 5+ ecosystem and was built from the ground up to support reactive programming using Project Reactor.Unlike Spring MVC (which is built on the Servlet API), WebFlux can run on non-blocking runtimes such as:

  • Netty (default)
  • Undertow
  • Jetty
  • Servlet 3.1 containers (like Tomcat with async support)

Key Features:

✅ Non-blocking and asynchronous request handling
✅ Reactive Streams support (Publisher, Subscriber model)
✅ Built on Project Reactor (Flux, Mono)
✅ Flexible runtime: Netty, Undertow, Jetty
✅ Compatible with functional and annotation-based programming models

Typical WebFlux Controller Example:

@RestController
@RequestMapping("/users")
public class UserController {

    private final UserService userService;
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/{id}")
    public Mono<User> getUser(@PathVariable String id) {
        return userService.findUserById(id);
    }

    @GetMapping
    public Flux<User> getAllUsers() {
        return userService.findAllUsers();
    }
}

Here:

  • Mono<User> represents a single async result (like a promise).
  • Flux<User> represents a stream of results (multiple users).

No thread is blocked while waiting for data to arrive — that’s the beauty of WebFlux.


Project Reactor

Project Reactor is at the core of Spring WebFlux. It implements the Reactive Streams specification, providing a set of interfaces for asynchronous stream processing with non-blocking backpressure.

Reactive Streams Core Interfaces:

  • Publisher – Emits data
  • Subscriber – Consumes data
  • Subscription – Connects publisher and subscriber
  • Processor – Acts as both Publisher and Subscriber

Reactor provides two main types of Publishers:

  1. Mono – Emits 0 or 1 element
  2. Flux – Emits 0 to N elements

Both are lazy — they don’t do anything until there’s a subscriber.


Flux and Mono Explained

Think of Flux and Mono as the backbone of reactive programming in WebFlux.

🧩 Mono (0 or 1 element)

Mono is used for operations that return a single value or no value at all — like fetching one record, saving data, or completing an operation.

Example:

Mono<String> message = Mono.just("Hello World !");
message.subscribe(System.out::println);

Output:

Hello World!

Example for asynchronous DB call:

Mono<User> userMono = userRepository.findById("123");
userMono.subscribe(user -> System.out.println(user.getName()));

🌪️ Flux (0 to N elements)

Flux represents a stream of data that can emit multiple elements — like reading a list of users, streaming logs, or continuous event streams.

Example:

Flux<Integer> numbers = Flux.range(1, 5);
numbers.subscribe(System.out::println);

Output:

1
2
3
4
5

Reactive Pipeline Example

Reactive pipelines are functional and declarative, allowing you to chain transformations.

Flux<String> names = Flux.just("Jack", "DJ", "Maya", "Rohit");

names
    .filter(name -> name.startsWith("D"))
    .map(String::toUpperCase)
    .subscribe(System.out::println);

Output:

DJ

Here:

  • The stream is processed asynchronously.
  • Operations are non-blocking.
  • The pipeline executes only when .subscribe() is called.

How Netty Powers Non-Blocking I/O

Spring WebFlux, by default, uses Netty — an asynchronous, event-driven network application framework. Netty is a non-blocking network framework built on top of Java NIO (New I/O). It enables high-performance and low-latency applications.

Instead of assigning a dedicated thread per request, Netty uses a small set of event loops to manage thousands of connections efficiently.


Netty’s Event Loop Model

Here’s how it works:

  1. EventLoop is a single-threaded loop responsible for handling multiple channels (connections).
  2. Each channel represents a connection.
  3. The EventLoopGroup manages multiple EventLoops.
  4. Each EventLoop handles:
    • Accepting new connections
    • Reading incoming data
    • Writing responses
    • Triggering callbacks when operations complete

This allows Netty to scale efficiently with minimal thread usage.


Why Event Loops Matter

In traditional blocking I/O:

  • Each request → one thread.
  • Thousands of connections → thousands of threads → context switching overhead.

In Netty’s non-blocking I/O:

  • Thousands of requests → handled by a handful of event loop threads.
  • No blocking or waiting → just event-driven callbacks.
  • Reduced context switching → faster performance and better scalability.

In short: One event loop can handle thousands of concurrent connections using asynchronous callbacks.


How WebFlux Uses Netty

Spring WebFlux uses Reactor Netty — a reactive programming adaptation of Netty.

It integrates seamlessly with Project Reactor, using Flux and Mono to represent asynchronous HTTP requests and responses.

Example:

RouterFunction<ServerResponse> route = RouterFunctions
    .route(GET("/hello"),
        request -> ServerResponse.ok().body(Mono.just("Hello Reactive World!"), String.class)
    );

This functional style is fully non-blocking. Each request is processed asynchronously by Netty’s event loop, and responses are streamed reactively.


Functional Endpoints vs Annotation-Based Controllers

Spring WebFlux offers two programming models:

1. Annotation-based (similar to Spring MVC)

@RestController
public class HelloController {

    @GetMapping("/hello")
    public Mono<String> sayHello() {
        return Mono.just("Hello WebFlux!");
    }
}

2. Functional Routing (more reactive style)

@Configuration
public class RouterConfig {
    @Bean
    public RouterFunction<ServerResponse> route() {
        return RouterFunctions
                .route(GET("/hello"), req -> ServerResponse.ok().bodyValue("Hello Reactive World!"));
    }
}

Functional routing gives you greater control and flexibility in defining reactive pipelines.


Thread Model Comparison

ModelFrameworkThreadingScalabilityNature
BlockingSpring MVCOne thread per requestLimited (depends on thread pool size)Synchronous
Non-BlockingSpring WebFluxEvent-loop basedHigh (handles thousands of concurrent users)Asynchronous

Backpressure Handling

A crucial aspect of reactive systems is backpressure — the ability to handle producers that emit data faster than consumers can process it.

Project Reactor implements backpressure automatically, ensuring system stability.

Example:

Flux.range(1, 1000)
    .log()
    .limitRate(10)
    .subscribe(System.out::println);

Here, only 10 elements are requested at a time, preventing overload.


Practical Example: Non-Blocking Service Integration

Let’s say you’re calling two external APIs and want to combine their results reactively.

public Mono<ResponseDTO> getCombinedData() {
    Mono<User> userMono = webClient.get().uri("/user/123").retrieve().bodyToMono(User.class);
    Mono<Order> orderMono = webClient.get().uri("/order/123").retrieve().bodyToMono(Order.class);

    return Mono.zip(userMono, orderMono)
            .map(tuple -> new ResponseDTO(tuple.getT1(), tuple.getT2()));
}
  • WebClient is the non-blocking alternative to RestTemplate.
  • Mono.zip combines results when both calls complete.
  • No threads are blocked waiting for API responses.

Even if the remote API takes time to respond, the server thread isn’t blocked — it’s free to handle other requests.


System Design Perspective

As a System Designer, WebFlux makes perfect sense for:

  • High-concurrency microservices
  • Real-time streaming (e.g., stock feeds, chat, analytics)
  • API Gateways
  • IoT systems where thousands of devices connect simultaneously
  • Serverless and cloud-native apps

When designed properly, a WebFlux-based system can handle tens of thousands of concurrent connections with just a few hundred threads.


When Not to Use WebFlux

While WebFlux is powerful, it’s not a silver bullet.

Avoid it if:

  • Your system is heavy computation and does not have any blocking IO calls.
  • You rely on blocking APIs (traditional JDBC, legacy code).
  • You have low concurrency requirements and simplicity is preferred.

In those cases, traditional Spring MVC may be simpler and equally effective.


Summary: Why Choose WebFlux?

FeatureSpring MVCSpring WebFlux
I/O ModelBlockingNon-blocking
ConcurrencyThread per requestEvent loop
ScalabilityLimitedHigh
Suitable ForTraditional appsReactive systems
Built OnServlet APIProject Reactor / Reactive Streams
Default RuntimeTomcatNetty

Conclusion

The shift toward non-blocking, reactive programming is no longer just a trend — it’s the foundation for modern, scalable system design.

Spring WebFlux, powered by Project Reactor and Netty’s event loop, offers an elegant way to build asynchronous, resilient APIs without compromising code readability or maintainability.

As backend systems move towards event-driven, microservices-based architectures, adopting WebFlux ensures your applications are future-ready — capable of handling massive concurrency with minimal resource footprint.

So if you’re designing the next generation of real-time, cloud-native systems — it’s time to go reactive.


References

Spring & WebFlux


Netty, Reactor, Event Loop & Pattern



Concurrency, Threading & Practical Concerns

  1. Threading Model of Spring WebFlux and Reactor — GeeksforGeeks
    Explains how Netty / Undertow event loops delegate I/O and reactive pipelines.
    https://www.geeksforgeeks.org/threading-model-of-spring-webflux-and-reactor/ GeeksforGeeks
  2. Concurrency in Spring WebFlux — Baeldung
    How WebClient shares the event loop, scheduling, and thread contexts.
    https://www.baeldung.com/spring-webflux-concurrency Baeldung on Kotlin
  3. Blocking I/O operation with WebFlux — StackOverflow
    Example discussion of integrating blocking APIs within a reactive pipeline (using publishOn, etc.).
    https://stackoverflow.com/questions/52945696/blocking-i-o-operation-with-webflux Stack Overflow
  4. Spring WebFlux: Refactoring Blocking API with Reactive API — StackOverflow
    On when you might need to offload blocking work, using Schedulers.boundedElastic(), etc.
    https://stackoverflow.com/questions/72914528/spring-webflux-refactoring-blocking-api-with-reactive-api-or-should-i Stack Overflow

Leave a Reply

Your email address will not be published. Required fields are marked *