Scenario — WebFlux Event Loop Stalling Under Load
Fast recall
Quick revision
Core answer
reactor-http-epoll-N threads are doing; enable BlockHound in non-prod to catch the culprit with a stack trace; check reactor.netty.eventloop.pending.tasks plus p99 latency; and audit code paths for blocking JDBC, synchronous SDKs, Thread.sleep, .block(), or CPU-heavy work on the event loop. Mitigation: wrap blocking calls with Schedulers.boundedElastic(), replace blocking clients with WebClient or R2DBC, and add circuit breakers. Long-term: consider migrating to virtual threads if the service is mostly blocking anyway.Real WebFlux production debugging, scheduler model understanding, and pragmatic prevention strategy
Adding more event-loop threads, increasing timeouts, or chasing symptoms without identifying the blocking call
Discuss BlockHound in CI, reactor-netty metrics, scheduler isolation, degrade-gracefully patterns, and when to consider virtual threads instead
Interview-ready guide
Interview answer
Event-loop stalls in WebFlux mean blocking work on the wrong scheduler — event-loop threads must return immediately.
Symptom pattern
- p99 spikes to seconds; p50 maybe OK
- Random timeouts; CPU modest but requests queue
- Thread dump shows
reactor-http-epoll-NthreadsRUNNABLEin user code, notepollWait reactor.netty.eventloop.pending.taskshigh
Step 1 — Diagnose
- Three thread dumps 10s apart
- Identify event-loop threads (
reactor-http-epoll-N) - Read the frames — JDBC /
SocketInputStream.read= blocking I/O; JSON / crypto = CPU;LockSupport.park= contention - Capture metrics — pending tasks, p99, pool saturation, GC pauses
Step 2 — Confirm with BlockHound in non-prod
- JVM agent throws on blocking calls on non-blocking schedulers
- Install only on dev / staging profile
- Reproduce under load with k6 / Gatling; correlate spikes with BlockHound errors
Step 3 — Fix categories
- Blocking JDBC —
Mono.fromCallable(...).subscribeOn(boundedElastic())or migrate to R2DBC - Synchronous HTTP — replace
RestTemplatewithWebClient - Synchronous SDKs — AWS v2 async +
Mono.fromFuture - CPU-heavy —
publishOn(Schedulers.parallel()) .block()in chain — replace withflatMapsynchronizedwith I/O — fetch outside lock; useReentrantLockorputIfAbsent- Bulk work on one thread —
flatMap(fn, 32)orparallel().runOn(parallel())
Step 4 — Prevent and monitor
- Metrics —
reactor.netty.eventloop.pending.tasks, p99 per endpoint - Alerts — pending greater than 100; p99 2x baseline; BlockHound errors
- BlockHound mandatory in staging CI
- ArchUnit lint banning
.block()in reactive packages - Degrade gracefully — rate limiter, bulkhead, circuit breaker, timeouts, fallbacks
Hard-earned lessons
- Reactive is not self-protecting; tooling and discipline are required
- Mixing blocking and reactive is fragile
- More threads is not the fix
boundedElastichas limits — don't push everything blocking there- Timeouts on every outbound call
- Consider MVC + virtual threads if you keep wrapping blocking calls
The one-line summary
- Event-loop stalls are blocking code on the wrong scheduler
- Find with BlockHound and thread dumps
- Move to
boundedElasticor replace with reactive alternatives - Prevent with tooling
- Consider virtual threads if reactive isn't paying off
Scenario — WebFlux Event Loop Stalling Under Load
Direct Answer
Event-loop stalls in WebFlux mean blocking work on the wrong scheduler — event-loop threads are sacred and must return immediately. The investigation: capture a thread dump during the spike to see what reactor-http-epoll-N threads are doing; enable BlockHound in non-prod to catch the culprit with a stack trace; check reactor.netty.eventloop.pending.tasks plus p99 latency; and audit code paths for blocking JDBC, synchronous SDKs, Thread.sleep, .block(), or CPU-heavy work on the event loop. Mitigation: wrap blocking calls with Schedulers.boundedElastic(), replace blocking clients with WebClient or R2DBC, and add circuit breakers. Long-term: consider migrating to virtual threads if the service is mostly blocking anyway.
Common Mistakes & Pitfalls
Adding more event-loop threads, increasing timeouts, or chasing symptoms without identifying the blocking call
Check your understanding
Answer these without looking back at the article.
- How do you configure BlockHound in Spring Boot tests vs production?
- What's the difference between boundedElastic and parallel schedulers?
- When would you decide to migrate from WebFlux to MVC + virtual threads?
- How do you prevent .block() calls from sneaking into a reactive codebase?
Related interview prep categories
1Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.