🔴 The Error You're Seeing

Confirm this matches your console output. If it does, you're in the right place.

ERROR LOG2026-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)

Solution 1✓ Most common cause

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); }
Solution 2

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; }) ); }
Solution 3

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()); }
Solution 4

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 { ... }
Solution 5

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

Spring Boot 2.x

Uses Reactor Netty. Block detection is lenient.

Spring Boot 3.x

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.