skip to content

Reactor Netty Event Loop

Reactor Netty runs a small pool of event-loop threads that multiplex non-blocking I/O across many connections. Knowing the thread names and the pool sizing is what lets you read a WebFlux thread dump.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

What server runs a Spring WebFlux application by default, and what kind of threads handles incoming requests?

level: juniorimportance: must knowfreq 70%

answer

  1. Reactor Netty = default WebFlux server
  2. reactor-http-nio-* event loops
  3. few threads, many connections (multiplex)
  4. one-thread-per-request is Tomcat/MVC, not this
  5. never block the event loop

basics

~10 s

By default WebFlux runs on Reactor Netty, an async non-blocking server. Requests are handled by a small pool of event-loop threads named reactor-http-nio-* instead of one thread per request.

solid answer

~40 s

Spring WebFlux ships with Reactor Netty as its default embedded server (pull in spring-boot-starter-webflux). Unlike Spring MVC on Tomcat, which dedicates one thread per request from a large pool, Reactor Netty uses a small fixed set of event-loop worker threads named reactor-http-nio-N. Each event loop multiplexes many connections using non-blocking I/O: it registers interest in read/write readiness and processes events as they arrive, never blocking on a socket. By default the number of event loops equals the number of CPU cores (min 4). This is why WebFlux can serve high concurrency with few threads — but also why you must never run blocking code on those threads. You can swap Netty for Jetty, Undertow, or Servlet 3.1+ Tomcat by changing the starter dependency.

code

java · 12 lines
java
@RestController
class PingController {

    // Runs on a reactor-http-nio-* event-loop thread.
    // Log the thread name to prove it.
    @GetMapping("/ping")
    Mono<String> ping() {
        return Mono.fromSupplier(() ->
                "handled on " + Thread.currentThread().getName());
        // -> "handled on reactor-http-nio-3"
    }
}

go deeper

for a junior

Must know: Reactor Netty is the default, threads are named reactor-http-nio-*, and you should not block them.

for a middle

Should articulate event-loop vs thread-per-request and the default sizing (CPU cores, min 4).

for a senior

Explains I/O multiplexing and the operational failure mode of a blocked event loop; knows how to offload.

for a principal

Frames server choice as an SPI decision and reasons about when Netty vs a Servlet container is the right runtime.

## What is Reactor Netty? **Reactor Netty** is a reactive networking library built on top of **Netty** (a mature Java NIO framework) and integrated with **Project Reactor** (the reactive-streams library behind Spring WebFlux). When you add `spring-boot-starter-webflux` and do not add another server, Spring Boot auto-configures Reactor Netty as the embedded HTTP server. This is the default because WebFlux is designed around non-blocking, reactive request handling. ## Event loop vs thread-per-request Traditional Spring MVC on **Tomcat** uses a **thread-per-request** model: a large pool (e.g. 200 threads) hands one thread to each request; that thread is occupied — even while blocked waiting on a database or remote call — until the response is written. Reactor Netty instead uses the **event-loop** model: - A **small, fixed** number of worker threads (the *event loops*), named `reactor-http-nio-1`, `reactor-http-nio-2`, …. - Each event loop owns a set of connections (sockets). Using the OS's I/O readiness mechanism (epoll on Linux, kqueue on macOS, or Java NIO `Selector`), one thread watches many sockets at once — **I/O multiplexing**. - When a socket becomes readable/writable, the loop runs the associated handler *briefly*, then moves on to the next ready socket. The thread is never parked waiting on a single connection. ## Default sizing The default number of event loops is `max(4, number of available processors)` — governed by the system property `reactor.netty.ioWorkerCount` and Reactor Netty's `LoopResources`. A handful of threads can therefore serve thousands of concurrent connections, because threads are only busy while actual work is ready. ## The cardinal rule Because so few threads carry all traffic, **blocking one event-loop thread stalls every connection it owns**. Never call blocking JDBC, `Thread.sleep`, `.block()`, or synchronous HTTP clients on a `reactor-http-nio-*` thread. Offload unavoidable blocking work with `.subscribeOn(Schedulers.boundedElastic())`. ## Swapping the server WebFlux is server-agnostic via the **`ReactiveWebServerFactory`** SPI. Exclude Netty and add `spring-boot-starter-jetty`, `-undertow`, or `-tomcat` to run on those instead — WebFlux adapts each to its reactive `HttpHandler`. ## When to care Knowing the default matters the moment you debug latency (`reactor-http-nio` threads pegged at 100% often means blocking code) or tune throughput.

  • How does the default server differ between Spring MVC and Spring WebFlux?
    MVC defaults to Tomcat with a large thread-per-request pool; WebFlux defaults to Reactor Netty with a small event-loop pool doing non-blocking I/O. MVC blocks a thread per in-flight request; Netty multiplexes many connections per thread.
  • Can you run WebFlux on Tomcat instead of Netty?
    Yes. Exclude Netty and add spring-boot-starter-tomcat (or Jetty/Undertow). WebFlux runs on any Servlet 3.1+ async container or those reactive servers via the ReactiveWebServerFactory abstraction — it does not require Netty.

saying these in an interview costs you the question

  • Saying WebFlux uses Tomcat by default
  • Claiming there is one thread per request in Netty
  • Thinking more event-loop threads always means more throughput
  • Believing blocking on an event-loop thread is harmless

context

open as a page

Why must you never run blocking code on a reactor-http-nio thread, and what do you do when you have an unavoidable blocking call?

level: middleimportance: must knowfreq 75%

basics

~10 s

Event-loop threads are few and shared across many connections. Blocking one freezes every request it serves. Offload blocking work to a separate scheduler with .subscribeOn(Schedulers.boundedElastic()).

open as a page

How does Spring WebFlux bootstrap onto Reactor Netty — what is the role of HttpHandler and ReactiveWebServerFactory?

level: seniorimportance: should knowfreq 45%

basics

~10 s

WebFlux compiles your routes/controllers into a single HttpHandler — a server-agnostic reactive request contract. A ReactiveWebServerFactory (NettyReactiveWebServerFactory by default) adapts that HttpHandler onto Reactor Netty and starts the server.

open as a page

Explain how a single Reactor Netty event-loop thread can handle thousands of concurrent connections — what is non-blocking I/O multiplexing?

level: seniorimportance: should knowfreq 40%

basics

~20 s

The event loop uses the OS's readiness notification (epoll/kqueue/NIO Selector) to watch many sockets at once. It only touches a connection when data is actually ready, so one thread services many connections without ever blocking on any single one.

open as a page

How is the Reactor Netty event-loop pool sized, and how and why would you customize LoopResources in a Spring Boot WebFlux app?

level: principalimportance: nice to knowfreq 25%

basics

~20 s

By default Reactor Netty creates max(4, CPU cores) event-loop threads, controlled by the reactor.netty.ioWorkerCount property and LoopResources. You rarely change it — the point is to match cores. Customize via a NettyServerCustomizer that calls runOn(LoopResources.create(...)).

open as a page