All Articles
Opinion·5 min read

Stop Reaching for WebFlux by Default

Reactive Spring has its place, but most teams pay its complexity tax for benefits they never needed — and virtual threads changed the math again.

Part of: Advanced Java Development

Somewhere along the way, reactive became the "serious" choice. New Spring services default to WebFlux because it sounds more scalable. Most of them shouldn't. Reactive programming solves a real problem, but it is not the problem most teams have.

What WebFlux Buys You

WebFlux lets a handful of threads serve enormous numbers of concurrent connections by never blocking. For a gateway fronting thousands of slow, long-lived streams, that is genuinely valuable. If you are building an API gateway or a service that fans out to dozens of downstream calls per request, reactive earns its keep.

What It Costs You

Everything else gets harder:

// Imperative — anyone can read this public Order load(Long id) { Order order = repo.findById(id).orElseThrow(); order.setLines(lineRepo.findByOrder(id)); return order; } // Reactive — now everything is a Mono/Flux, forever public Mono<Order> load(Long id) { return repo.findById(id) .switchIfEmpty(Mono.error(new NotFound())) .flatMap(order -> lineRepo.findByOrder(id) .collectList() .map(lines -> { order.setLines(lines); return order; })); }

Stack traces become useless. Debugging is painful. Every developer must think in operators. A single blocking call sneaked into the chain quietly ruins the whole benefit. And your entire stack — driver, database, libraries — must be reactive end to end, or it collapses to blocking anyway.

Virtual Threads Changed the Calculus

The main historical reason to accept that pain was thread efficiency: you couldn't afford one blocking thread per request at high concurrency. Virtual threads removed that constraint. You can now write plain, imperative, blocking code and still handle massive concurrency. The chief argument for WebFlux in ordinary CRUD services has largely evaporated.

Where I Land

Reach for WebFlux when you have a genuinely streaming, high-fan-out, backpressure-sensitive workload and a fully reactive stack to match. For the standard request/response service — which is most services — write imperative code on virtual threads. It is easier to read, easier to debug, and now scales just fine.

Default to simple. Make reactive a deliberate choice you can justify, not the reflex.

#Java#Spring#WebFlux#Architecture

Want to go deeper?

Advanced Java Development

View Course