Building Reactive Microservices with Spring WebFlux
title: “Building Reactive Microservices with Spring WebFlux” date: 2025-01-15 tags: [“Spring WebFlux”, “Reactive Programming”, “Microservices”, “Java”, “Reactor”] categories: [“Java”]
Building Reactive Microservices with Spring WebFlux
In the modern era of cloud-native applications, traditional blocking I/O models are increasingly becoming a bottleneck. Every thread waiting for a database query, an HTTP call, or a file read represents wasted resources. Spring WebFlux, introduced in Spring 5, offers a paradigm shift: a fully non-blocking, reactive stack built on Project Reactor. This post dives deep into building reactive microservices with Spring WebFlux, covering architecture, practical implementation, testing, and common pitfalls.
Why Reactive? The Performance Imperative
Before we jump into code, let’s understand the “why.” Traditional Spring MVC uses a thread-per-request model. Under load, this leads to thread pool exhaustion, context switching overhead, and ultimately, degraded throughput. Reactive systems, on the other hand, use a small, fixed number of threads and rely on event-driven, asynchronous processing.
Consider a microservice that calls three downstream services. With blocking I/O, each request ties up a thread for the entire duration. With WebFlux, the thread is freed while waiting for responses, allowing it to handle other requests. This results in better resource utilization and higher concurrency with fewer threads.
Spring WebFlux Architecture
Spring WebFlux supports two programming models:
- Annotation-based: Similar to Spring MVC, using
@RestController,@RequestMapping, etc. - Functional endpoints: A more explicit, lambda-based routing DSL.
Both models are built on top of Reactor’s Flux (for 0..N elements) and Mono (for 0..1 elements). The key is that these types are reactive — they don’t block; instead, they notify subscribers when data is available.
Core Dependencies
To get started, add the WebFlux starter to your pom.xml:
1 | <dependency> |
Building a Reactive REST API
Let’s build a simple user management service. We’ll use an embedded MongoDB with the reactive Spring Data MongoDB driver.
Domain Model
1 | import org.springframework.data.annotation.Id; |
Reactive Repository
Spring Data provides reactive repositories that return Flux and Mono:
1 | import org.springframework.data.mongodb.repository.ReactiveMongoRepository; |
Controller with WebFlux
1 | import org.springframework.web.bind.annotation.*; |
Notice that the return types are Flux and Mono. The framework handles subscription and backpressure automatically.
Reactive Communication Between Services
Microservices often need to talk to each other. With WebFlux, you use WebClient — a non-blocking HTTP client.
Configuring WebClient
1 | import org.springframework.context.annotation.Bean; |
Making Reactive Calls
1 | import reactor.core.publisher.Mono; |
WebClient is fully reactive. It doesn’t block while waiting for the response, freeing the thread to handle other requests.
Functional Endpoints: An Alternative Approach
For more explicit control, you can use the functional programming model:
1 | import org.springframework.context.annotation.Bean; |
Functional endpoints are great for fine-grained control over error handling and request processing.
Error Handling in Reactive Streams
Error handling in reactive programming is different from traditional try-catch. You use operators like onErrorReturn, onErrorResume, and onErrorMap.
1 |
|
For global error handling, implement ErrorWebExceptionHandler:
1 |
|
Backpressure and Resilience
Backpressure is a core concept in reactive systems — it’s the ability of the consumer to signal the producer to slow down. Reactor handles this automatically, but you can customize it.
1 | Flux.range(1, 1000) |
For resilience, combine WebFlux with Resilience4j:
1 | import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; |
Testing Reactive Microservices
Testing reactive code requires StepVerifier from reactor-test:
1 | import org.junit.jupiter.api.Test; |
For integration testing with WebTestClient:
1 |
|
Common Pitfalls and How to Avoid Them
1. Blocking Calls in Reactive Chains
This is the #1 mistake. Never call .block() inside a reactive pipeline:
1 | // WRONG: blocks the reactive thread |
2. Forgetting to Subscribe
Reactive streams are lazy — nothing happens until you subscribe. In a WebFlux controller, the framework handles subscription, but in standalone code, you must subscribe:
1 | repository.findAll().subscribe(System.out::println); |
3. Mixing Reactive and Blocking Databases
If your database driver is blocking (e.g., JDBC), it will block the event loop. Use reactive drivers like r2dbc for relational databases, or spring-data-mongodb-reactive, spring-data-cassandra-reactive, etc.
4. Ignoring Backpressure
While Reactor handles backpressure, badly designed consumers can cause OutOfMemoryError. Use limitRate() to control demand:
1 | repository.findAll() |
Performance Tuning
- Thread model: WebFlux runs on Netty by default. Tune
reactor.netty.ioWorkerCountandreactor.netty.ioSelectCount. - Database connection pool: Use reactive connection pools like
r2dbc-pool. - Serialization: Jackson is the default, but for high throughput, consider
kryoor protocol buffers.
Example application.yml configuration:
1 | spring: |
When to Use WebFlux vs WebMVC
WebFlux is not always the answer. Use it when:
- You have high concurrency requirements (thousands of concurrent connections)
- You’re building streaming services or long-lived connections (SSE, WebSockets)
- Your entire stack is reactive (database, messaging, etc.)
Stick with WebMVC when:
- You have a simple CRUD app with low traffic
- Your team is unfamiliar with reactive programming
- You rely on blocking libraries (e.g., JDBC, JPA)
Key Takeaways
- Spring WebFlux provides a fully non-blocking, reactive stack for building microservices, leveraging Project Reactor’s
MonoandFlux. - Use
WebClientfor reactive inter-service communication — neverRestTemplatein a reactive context. - Always avoid blocking calls within reactive pipelines; use
subscribeOnwith a dedicated scheduler for blocking operations. - Functional endpoints offer more explicit control compared to annotation-based controllers.
- Test reactive code with
StepVerifierandWebTestClientto verify asynchronous behavior. - Combine WebFlux with resilience patterns (circuit breakers, retries) to build robust microservices.
- Choose WebFlux when you need high concurrency and have a fully reactive stack; prefer WebMVC for simpler, blocking scenarios.