Reactive Microservices with Spring WebFlux
Building Fully Non-Blocking REST APIs with Reactor and WebFlux
Modern applications are under constant pressure to serve thousands of concurrent requests while remaining fast and responsive. Traditional servlet-based stacks like Spring MVC, while reliable, are fundamentally synchronous and thread-blocking. As systems scale, this model can become a bottleneck.
Spring WebFlux provides a reactive, non-blocking foundation for building high-throughput microservices that scale gracefully under load. In this article, you’ll learn what makes WebFlux different, why you should consider it, and how to build a reactive REST API step by step.
1. What is Spring WebFlux?
Spring WebFlux is a reactive web framework introduced in Spring 5. It supports building applications on:
- Reactive Streams: A specification for asynchronous data processing with backpressure.
- Project Reactor: A reactive library providing
MonoandFluxtypes. - Non-blocking runtimes: Netty (default), Undertow, or servlet containers in asynchronous mode.
With WebFlux, you no longer block a thread per request. Instead, you declare reactive pipelines that process data asynchronously, letting the runtime handle efficient scheduling.
2. Why Go Reactive?
Traditional Spring MVC is perfectly fine for many use cases, but if you need:
✅ High concurrency (thousands of requests)
✅ Streaming data over HTTP
✅ Backpressure handling
✅ Better resource utilization under load
… then WebFlux shines. For example, a small pool of threads can handle many I/O-bound operations concurrently because threads aren’t blocked waiting for responses.
3. Core Concepts
Mono and Flux
At the heart of WebFlux is Project Reactor:
Mono<T>– 0..1 values (e.g., a single result or empty)Flux<T>– 0..N values (e.g., a stream)
Think of them as reactive equivalents to Optional and List, but asynchronous.
Backpressure
Backpressure allows the consumer to control the rate of data emission, preventing overload.
4. Creating a Reactive REST API
Let’s build a simple microservice exposing a reactive REST API.
1️⃣ Setup Your Project
In Maven, include:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-core</artifactId> </dependency>
Spring Boot will auto-configure a Netty-based reactive server.
2️⃣ Define a Reactive Repository
You can integrate with reactive data stores (like MongoDB). For simplicity, here’s an in-memory example:
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface PersonRepository {
Flux<Person> findAll();
Mono<Person> findById(String id);
Mono<Person> save(Person person);
}
A simple implementation:
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class InMemoryPersonRepository implements PersonRepository {
private final Map<String, Person> store = new ConcurrentHashMap<>();
@Override
public Flux<Person> findAll() {
return Flux.fromIterable(store.values());
}
@Override
public Mono<Person> findById(String id) {
return Mono.justOrEmpty(store.get(id));
}
@Override
public Mono<Person> save(Person person) {
store.put(person.id(), person);
return Mono.just(person);
}
}
3️⃣ Define a Reactive Controller
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/api/people")
public class PersonController {
private final PersonRepository repository;
public PersonController(PersonRepository repository) {
this.repository = repository;
}
@GetMapping
public Flux<Person> getAll() {
return repository.findAll();
}
@GetMapping("/{id}")
public Mono<Person> getById(@PathVariable String id) {
return repository.findById(id);
}
@PostMapping
public Mono<Person> create(@RequestBody Person person) {
return repository.save(person);
}
}
4️⃣ Streaming Data
Reactive APIs are great for server-sent events:
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Person> streamPeople() {
return repository.findAll()
.delayElements(Duration.ofSeconds(1)); // Simulate streaming
}
Calling /api/people/stream will emit one person per second.
5. Testing the API
Use curl:
curl http://localhost:8080/api/people
Or test streaming:
curl http://localhost:8080/api/people/stream
6. Synchronous vs. Reactive: A Quick Comparison
| Aspect | Spring MVC | Spring WebFlux |
|---|---|---|
| Programming Model | Servlet (synchronous) | Reactive Streams (async) |
| Thread Model | One thread per request | Event loop (non-blocking) |
| Backpressure | No | Yes |
| Data types | List, Optional | Flux, Mono |
| Scalability | Limited by thread pool size | High concurrency |
7. Best Practices and Opinions
✅ Start Reactive Only if Needed
Not all workloads benefit. Use it primarily for I/O-bound, high-concurrency cases.
✅ Avoid Mixing Blocking Calls
Never call blocking APIs inside reactive pipelines (.block(), JDBC). This negates the benefits.
✅ Leverage Reactive Databases
Use reactive drivers (R2DBC, Mongo Reactive) to keep the stack non-blocking end to end.
✅ Think in Streams
Model flows as sequences and transformations, not imperative steps.
✅ Test with StepVerifier
Use Reactor’s StepVerifier to write clear, deterministic tests.
8. Conclusion
Spring WebFlux empowers you to build modern, scalable microservices that thrive under heavy load. By embracing the reactive paradigm with Project Reactor, you can handle streaming data, maintain backpressure, and achieve higher throughput with fewer resources.
While reactive programming has a learning curve, it’s a powerful tool in your Spring Boot toolkit—and one that pays off when you need responsiveness and scalability.
Further Reading





