Reactive Programming in Spring: Introduction
Why blocking RestTemplate calls limit scalability, how WebClient, Mono and Flux avoid it, what backpressure is, and when reactive isn't worth it.
Archive note I wrote this in 2020 on my old blog. It moved here in 2026 with the code and diagrams redone; library versions and APIs may have changed since. Original post.
Contents
In this article we’ll take a short look at reactive programming, and at why the Spring team put RestTemplate into maintenance mode in favour of WebClient, introduced in Spring 5.
Here’s how the Spring docs define reactive programming:
Reactive programming is about non-blocking applications that are asynchronous and event-driven and require a small number of threads to scale vertically rather than horizontally.
Blocking vs non-blocking
To understand non-blocking applications, let’s start with the traditional, blocking way of writing the code:
@Service
public class UserService {
@Autowired
RestTemplate restTemplate;
public UserResponseModel getUserDetailsBlocking(String id) {
UserResponseModel userModel = restTemplate.getForObject(
"http://localhost:8081/app/home/" + id, UserResponseModel.class);
System.out.println(userModel.getName());
return userModel;
}
}This calls http://localhost:8081/app/home/{id}, a service I made deliberately slow: it answers after 3 seconds. The application’s thread is blocked until the response comes back. That wastes hardware and limits scalability. Why?
A Spring Boot web application runs in a servlet container, which uses a thread per request: each incoming request gets a thread that sees it through to the end. The default pool has 200 threads. If they’re all calling a service that’s currently slow, they all sit in a WAITING state and new requests queue up. You can raise the pool from 200 to, say, 500, but every thread costs memory and CPU time for context switching. Horizontal scaling can help, up to a point.
Horizontal scaling
With horizontal scaling you run more instances of the application, for example as containers in Kubernetes, behind a load balancer. Each instance still has its default 200 threads, but five instances give you 1,000 in total.
But that doesn’t really solve the problem. If the application becomes popular, you keep adding instances, and paying for them in memory and CPU, all because a slow service is blocking your threads.
Non-blocking, asynchronous applications
WebClient arrived in Spring 5, as part of Spring WebFlux, as a non-blocking alternative to RestTemplate built for reactive programming. Here’s the same call written with it. First, a WebClient pointed at the service:
@Bean
public WebClient webClient() {
return WebClient.builder().baseUrl("http://localhost:8081/app").build();
}Then the service:
@Service
public class UserService {
@Autowired
WebClient client;
public Mono<UserResponseModel> getUserDetails(String id) {
System.out.println("ENTERS NON-BLOCKING METHOD");
Mono<UserResponseModel> monoUser = this.client.get().uri("/home/{id}", id)
.retrieve()
.bodyToMono(UserResponseModel.class);
// Prints the user name "Shahul" once the response is received
monoUser.subscribe(user -> {
System.out.println(user.getName());
});
System.out.println("NON-BLOCKING METHOD COMPLETED");
// Returns the response at a later point
return monoUser;
}
}It calls the same slow URL, http://localhost:8081/app/home/{id}. The output is:
ENTERS NON-BLOCKING METHOD
NON-BLOCKING METHOD COMPLETED
"Shahul"The method finishes before the name is printed: nothing waits for the slow service. Let’s look at the pieces involved.
Mono
Mono comes from Project Reactor, the reactive library Spring WebFlux is built on. It represents a value that will arrive at some point in the future, and has methods for working with that value when it does. You can wrap a response object in a Mono.
Flux
Flux is like Mono, but for many values. Use Mono for a single object and Flux when the response is a sequence of values. I haven’t used Flux here, to keep things simple.
The publisher–subscriber model
Mono is the publisher. When the data arrives from the slow service, it’s pushed to its subscribers. In the example I subscribe explicitly to print the name. When a browser calls an endpoint that returns a Mono, the framework subscribes on its behalf and streams the result to the browser as soon as it’s available.
The Reactive Streams specification defines this as a sequence of events. In our example:
Earlier I said reactive web applications are highly scalable and use resources efficiently. Here’s why:
In a reactive application every activity is an event, and the whole application is event-driven. As the diagram shows, a thread never sits waiting. The obvious benefit: far fewer threads.
The CPU doesn’t have to switch between lots of threads, and the thread pool can stay as small as the application needs, which takes load off the CPU.
Another important advantage of this model is backpressure.
Backpressure
Imagine a client that asks for every matching item in a database so it can process them. The backend has 1,000 results and is ready to send the whole list.
The client can’t process that much at once, so it asks for 50 at a time. That’s backpressure: the subscriber uses request(n) to tell the publisher how much it can handle. Because data arrives one event at a time, the publisher can respect that limit.
Does that mean everything should be reactive from now on? No. Reactive programming helps in some cases and not in others.
Disadvantages
Processing data is harder
Any processing on a Mono or Flux has to use the Reactor API’s operators. Everything is event-driven and based on streams of data, which is quite different from imperative code.
It has to be reactive end to end
The whole stack has to be reactive, including database drivers, so the database can notify the publisher when its data is ready, and every API call. A single blocking call anywhere defeats the purpose.
Debugging is harder
Stack traces are less helpful, and logs need care, because everything happens as events on shared threads.
It suits microservices more than monoliths
Reactive programming pays off mostly in microservices and distributed systems. In a monolith it’s usually less useful.
Conclusion
We’ve barely scratched the surface of reactive programming, but this should give you a solid footing for going further.
The projects are on GitHub: shahulbasha/reactiveapplication.
References
- Spring Framework reference: Spring WebFlux
- Defog Tech’s videos on YouTube