How does Spring WebFlux bootstrap onto Reactor Netty — what is the role of HttpHandler and ReactiveWebServerFactory?
answer
- HttpHandler = one reactive function for whole app
- WebHttpHandlerBuilder assembles it
- ReactiveWebServerFactory = server SPI (Netty default)
- ReactorHttpHandlerAdapter bridges to Netty HttpServer
- customizers tune HttpServer, not controllers
basics
~10 sWebFlux 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 sWebFlux 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// 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
Not expected to know the bootstrap internals.
Aware there is a server abstraction and Netty is pluggable.
Should name HttpHandler, ReactiveWebServerFactory, and the adapter, and explain the assembly and customization hooks.
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