🔴 The Error You're Seeing
Confirm this matches your console output. If it does, you're in the right place.
2026-02-20 18:40:05.200 ERROR 8842 --- [ctor-http-nio-2] o.s.w.s.adapter.HttpWebHandlerAdapter : [id: 1, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:12345] Failed to handle HTTP request
java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-2
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:102)⚡ Quick Fix Works 80% of the time
Return the Mono or Flux instead of calling .block().
// BAD
public User getUser() {
return webClient.get().retrieve().bodyToMono(User.class).block();
}
// GOOD
public Mono<User> getUser() {
return webClient.get().retrieve().bodyToMono(User.class);
}🧠 Why this Happens
Tap to expand the deep technical explanation
In Spring WebFlux, the event loop threads (reactor-http-nio) are strictly non-blocking. Calling `.block()` on a `Mono` or `Flux` inside a controller suspends the thread, which Reactor strictly detects and throws as an `IllegalStateException` to prevent thread starvation and freezing the server.
The HITEC City Parking Spot Analogy:
It's like a traffic cop stepping into the middle of a highway to read a book. They block all traffic, causing a massive jam. The police department (Reactor) immediately removes the cop to keep traffic flowing.
🔁 How to Reproduce Confirm this is your error
Create a WebFlux `@RestController`. In the method, call `webClient.get().retrieve().bodyToMono(String.class).block()`. Hit the endpoint.
🛠️ Solutions (5 Ways to Fix)
Return the Publisher instead of blocking
👉 Use this as the standard WebFlux pattern.
Instead of extracting the value with `.block()`, return the `Mono` or `Flux` directly. Spring WebFlux will subscribe to it and stream the response asynchronously.
@GetMapping("/user")
public Mono<User> getUser() {
return webClient.get().retrieve().bodyToMono(User.class);
}Use flatMap to chain reactive calls
👉 Use this if you need the result of one call to make another.
Use `flatMap` to execute the second call when the first completes, keeping the entire chain non-blocking.
public Mono<Order> getOrder(String id) {
return repo.findById(id)
.flatMap(order -> webClient.get().retrieve().bodyToMono(Details.class)
.map(details -> { order.setDetails(details); return order; })
);
}Run blocking code on a separate thread
👉 Use this if you absolutely must call a blocking legacy library (like JDBC).
Move the blocking call off the event loop thread using `subscribeOn` or `Schedulers.boundedElastic()`.
public Mono<User> getUser() {
return Mono.fromCallable(() -> blockingJdbcCall())
.subscribeOn(Schedulers.boundedElastic());
}Change controller to @RestController (MVC)
👉 Use this if you are migrating to WebFlux but don't have time to rewrite all blocking logic.
If you use `@RestController` from Spring MVC instead of `@Controller` with WebFlux, Tomcat will use blocking threads, and `.block()` is allowed.
@RestController // Spring MVC, not WebFlux
public class MyController { ... }Use subscribe() for fire-and-forget
👉 Use this if you don't need the result to send back to the client.
If you just want to trigger a background task, use `.subscribe()` instead of `.block()`. The method will return immediately.
public void triggerTask() {
webClient.get().retrieve().bodyToMono(String.class).subscribe(result -> {
System.out.println(result);
});
}📋 Version Notes
Uses Reactor Netty. Block detection is lenient.
Stricter block detection. Fails immediately on reactor-http-nio threads.
🛡️ How to Prevent This Next Time
When writing WebFlux code, grep your codebase for `.block()`. It should only appear in tests or main() methods, never inside reactive controller or service methods.