Traditionally, an application maintains a pool of threads. As new requests come in, a thread gets assigned to handle each one.
The problem: one thread is allocated per request. If that request needs to call an external API, or run a long-running database query, the thread sits in a perpetual waiting state — it doesn’t do any other work. It just sends the request and waits for a response before it can respond to the user.
Each thread also needs up to 1MB of stack memory. With 1GB of RAM and 400 concurrent requests, that’s 400 threads — 400MB of memory just for the threads, before counting the memory needed to do the actual work.
Microservices solve some problems with traditional architectures, but introduce their own — like a lot of network calls — which worsens the thread-waiting problem.
This is where WebFlux comes in. It uses system resources efficiently with fewer threads. With WebFlux and reactive libraries, threads don’t sit waiting — network calls and requests happen in a non-blocking way: send the request, do other work while waiting, and get notified when the response comes back so the thread can pick up the pending work. Based on performance benchmarks, the throughput of WebFlux/reactive applications scales much better than traditional web applications under the same concurrent load.
With WebFlux, your backend returns a Publisher — either a Mono or a Flux. The browser acts
as the subscriber (or, in a microservice, whichever service calls your reactive controller).
Unlike normal blocking I/O, which returns data as a list (a data structure), reactive apps return
data as a Mono or Flux — publishers. What makes reactive special is that it can return data as
a stream, removing the need to make repeated requests or one big bulk request. The subscriber
receives data as the publisher publishes it — this is a Server-Sent Event.
A sample controller:
@GetMapping(value = "table/{input}/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<Response> multiplicationTableStream(@PathVariable int input) { return mathService.multiplicationTable(input);}And the service:
public Flux<Response> multiplicationTable(int input) { return Flux.range(1, 10) .delayElements(Duration.ofSeconds(1)) .doOnNext(i -> System.out.println("reactive math service processing " + i)) .map(i -> new Response(i * input));}This is a real advantage over traditional MVC: in MVC, when a browser requests data, the backend processes it, but nothing shows up until processing finishes — and if the user cancels the request in the browser, the backend has no idea and keeps processing anyway. In a reactive approach, as soon as the browser terminates the request, the backend stops processing too.