skip to content

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

level: seniorimportance: should knowfreq 45%

answer

  1. HttpHandler = one reactive function for whole app
  2. WebHttpHandlerBuilder assembles it
  3. ReactiveWebServerFactory = server SPI (Netty default)
  4. ReactorHttpHandlerAdapter bridges to Netty HttpServer
  5. customizers tune HttpServer, not controllers

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.

solid answer

~40 s

WebFlux separates the web framework from the server via two abstractions. HttpHandler is the lowest-level reactive contract: Mono<Void> handle(ServerHttpRequest, ServerHttpResponse). All of WebFlux — DispatcherHandler, annotated controllers, functional routes, filters — is assembled behind one HttpHandler by WebHttpHandlerBuilder. Servers are abstracted by ReactiveWebServerFactory; the default bean is NettyReactiveWebServerFactory, which wraps the HttpHandler in a ReactorHttpHandlerAdapter (a Netty-level handler) and boots Reactor Netty's HttpServer, binding to the configured port. Swapping to Jetty/Undertow/Tomcat just swaps the factory (JettyReactiveWebServerFactory etc.) — the same HttpHandler runs everywhere. Spring Boot's ReactiveWebServerFactoryAutoConfiguration picks Netty when it is on the classpath and no other factory is defined. You can customize the server via WebServerFactoryCustomizer or a ReactorNettyServerCustomizer bean.

code

java · 18 lines
java
// Manual bootstrap (no Spring Boot) shows the seams:
AnnotationConfigApplicationContext ctx =
        new AnnotationConfigApplicationContext(WebConfig.class);

// 1. Whole app -> a single reactive HttpHandler
HttpHandler handler = WebHttpHandlerBuilder.applicationContext(ctx).build();

// 2. Bridge the generic HttpHandler to Reactor Netty's handler type
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);

// 3. Build + start Reactor Netty HttpServer (spins up reactor-http-nio-* loops)
DisposableServer server = HttpServer.create()
        .host("0.0.0.0")
        .port(8080)
        .handle(adapter)
        .bindNow();

server.onDispose().block();

go deeper

for a junior

Not expected to know the bootstrap internals.

for a middle

Aware there is a server abstraction and Netty is pluggable.

for a senior

Should name HttpHandler, ReactiveWebServerFactory, and the adapter, and explain the assembly and customization hooks.

for a principal

Uses the layering to make runtime decisions (Netty vs Servlet container), reads WebFlux stack traces fluently, and knows the standalone wiring.

## The layered design WebFlux is deliberately **server-agnostic**. It defines its own minimal HTTP abstractions so the framework never depends on Netty directly. ### 1. `HttpHandler` — the contract `org.springframework.http.server.reactive.HttpHandler` is the single-method interface at the bottom of the stack: ``` Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response); ``` `ServerHttpRequest`/`ServerHttpResponse` are WebFlux's reactive, non-blocking request/response types (bodies are `Flux<DataBuffer>`). Everything above — the `DispatcherHandler` that routes to `@Controller`s, `RouterFunction`s, `WebFilter` chain, exception handling — is composed into **one** `HttpHandler` by **`WebHttpHandlerBuilder`** (`WebHttpHandlerBuilder.applicationContext(ctx).build()`). So the whole application presents to the server as a single reactive function. ### 2. `ReactiveWebServerFactory` — the server SPI `org.springframework.boot.web.reactive.server.ReactiveWebServerFactory` has `getWebServer(HttpHandler)` returning a startable `WebServer`. Implementations: - **`NettyReactiveWebServerFactory`** (default), - `JettyReactiveWebServerFactory`, `UndertowReactiveWebServerFactory`, `TomcatReactiveWebServerFactory`. ### 3. The adapter Each factory bridges the generic `HttpHandler` to its native server API. For Netty that bridge is **`ReactorHttpHandlerAdapter`**, which implements Reactor Netty's `BiFunction<HttpServerRequest, HttpServerResponse, Mono<Void>>` handler signature and delegates to your `HttpHandler`, translating Netty's request/response into `ServerHttpRequest`/`ServerHttpResponse`. The factory then builds a Reactor Netty **`HttpServer`**, sets the port/address, installs the adapter as the handler, and binds — spinning up the **event-loop** threads (`reactor-http-nio-*`). ## Boot auto-configuration flow In a Spring Boot reactive app: 1. `ReactiveWebServerFactoryAutoConfiguration` sees `HttpHandler` + a reactive web app + Netty on the classpath and registers a `NettyReactiveWebServerFactory` bean (unless you define another `ReactiveWebServerFactory`). 2. `ReactiveWebServerApplicationContext` calls `factory.getWebServer(httpHandler)` and starts it during refresh. 3. The server begins accepting connections on its event loops. ## Customization hooks - **`WebServerFactoryCustomizer<NettyReactiveWebServerFactory>`** — bean to tweak the factory (port, SSL, address). - **`NettyServerCustomizer`** (a.k.a. `ReactorNettyServerCustomizer`) — adjust the underlying Reactor Netty `HttpServer` (idle timeouts, `LoopResources`, HTTP/2, wiretap). - `application.properties`: `server.port`, `server.netty.*` (e.g., `server.netty.connection-timeout`, `server.netty.idle-timeout`). ## Standalone (no Boot) You can wire it by hand, which shows the seams clearly: ``` HttpHandler handler = WebHttpHandlerBuilder.applicationContext(ctx).build(); ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler); HttpServer.create().host("0.0.0.0").port(8080).handle(adapter).bindNow(); ``` ## Why this matters - It explains **how one codebase runs on four servers** unchanged. - It clarifies where to hook for low-level tuning (customizers operate on the `HttpServer`, not your controllers). - It demystifies stack traces: seeing `ReactorHttpHandlerAdapter` -> `HttpWebHandlerAdapter` -> `DispatcherHandler` is the normal WebFlux call chain. ## Gotcha `HttpHandler` is **not** the Servlet `HttpServlet`. WebFlux can also run on a Servlet 3.1+ container via a `ServletHttpHandlerAdapter`, but on Netty there is no Servlet API involved at all — it is pure reactive I/O.

  • If HttpHandler is server-agnostic, how does the same app run on Jetty instead of Netty?
    You swap only the ReactiveWebServerFactory (e.g., JettyReactiveWebServerFactory) via the dependency. It wraps the same HttpHandler in a Jetty-specific adapter and starts a Jetty server. The framework code, controllers, and HttpHandler are unchanged.
  • Where would you configure a custom idle timeout or LoopResources on Reactor Netty?
    Register a WebServerFactoryCustomizer<NettyReactiveWebServerFactory> (or add a NettyServerCustomizer) that calls addServerCustomizers on the factory to mutate the underlying HttpServer — e.g., httpServer.idleTimeout(...) or .runOn(LoopResources.create(...)).

saying these in an interview costs you the question

  • Claiming controllers talk to Netty directly with no abstraction
  • Confusing HttpHandler with the Servlet API / HttpServlet
  • Thinking you must rewrite the app to change servers
  • Believing WebHttpHandlerBuilder is per-request rather than one handler for the whole app

context