There might be situations where you need to invoke a blocking call within a reactive flow in Spring WebFlux. This can be an external service like a database API, file I/O, or another API. If you don’t handle this case with care, it can break the reactive flow. You don’t want to break the reactive thread model by invoking a block call. Let us look at the best practices in integrating a blocking service in a non-blocking application.
Spring WebFlux is designed for non-blocking, reactive programming, and it uses Project Reactor under the hood. Hence, all code should be non-blocking, or you need to put in place mechanisms to isolate blocking calls.
How to invoke a blocking call correctly in Spring WebFlux
Use Mono.fromCallable() and schedule it on a bounded elastic thread pool (which is designed for blocking I/O).
Mono.fromCallable(() -> {
// blocking call here
return repository.getData();
})
.subscribeOn(Schedulers.boundedElastic());
Code language: Java (java)
Why use Schedulers.boundedElastic() ?. It’s intended for blocking I/O operations, and it executes the blocking call in a separate thread managed by the elastic pool. It creates threads as needed (up to a limit) and reuses them. It prevents blocking the main reactive thread pool.
Don’t call a blocking method inside a .map() or .flatMap() without wrapping it in a Mono.fromCallable() as explained above. That would block the non-blocking threads and defeat the purpose of WebFlux.
We can also enhance the above example by adding exception handling and retries to make it more robust.
Mono.fromCallable(() -> repository.getData())
.subscribeOn(Schedulers.boundedElastic())
.timeout(Duration.ofSeconds(2))// Timeout if it takes too long
.retryWhen(Retry.backoff(2, Duration.ofMillis(500))) // Retry on failure with a backoff interval of 500 ms
.onErrorResume(e -> {
// Fallback or error handling logic
log.error("Error fetching data", e);
return Mono.just("default placeholder data");
});
Code language: Java (java)
You can look at the documentation in the project reactor. Link: Mono.fromCallable
An Overview of Webflux Threading Model
If you’re working with Spring WebFlux, it helps to understand how Reactor’s threading model works under the hood. Unlike traditional Spring MVC apps that spin up a thread per request, WebFlux is built on a non-blocking, event-loop model. It is similar to the Node.js event loop.
This means a small number of threads can handle many thousands of concurrent connections. Reactor uses something called Schedulers to manage different types of work: parallel() for CPU-bound tasks, boundedElastic() for blocking I/O (like calling a database or a legacy service). The main idea is: don’t block the event loop. If you accidentally run blocking code on the wrong thread, you could compromise your application’s performance without realizing it.
Leave a Reply