Trace a request from the reactive server down to the controller: how do HttpHandler, WebHandler, WebFilter, and DispatcherHandler fit together?
answer
- Server -> HttpHandler -> HttpWebHandlerAdapter -> WebFilters -> DispatcherHandler
- HttpHandler = server-agnostic seam
- ServerWebExchange created by HttpWebHandlerAdapter
- Security is a WebFilter
- WebHttpHandlerBuilder wires it from the context
basics
~20 sThe server (Netty) adapts the request into an HttpHandler. That delegates to a WebHandler chain: WebFilters run first (like security), exception handlers wrap it, and at the end DispatcherHandler dispatches to a controller. Each layer returns Mono<Void>.
solid answer
~40 sThere's a layered chain. The reactive server (Reactor Netty) speaks to an **`HttpHandler`** — the lowest, server-agnostic contract `Mono<Void> handle(ServerHttpRequest, ServerHttpResponse)`. Spring Boot builds this via `WebHttpHandlerBuilder`, which composes: an `HttpWebHandlerAdapter` that creates the **`ServerWebExchange`**, then a chain of **`WebFilter`s** (security, CORS, tracing), wrapped by **`WebExceptionHandler`s**, terminating in the target **`WebHandler`** — which is the **`DispatcherHandler`**. So the order is: server → HttpHandler(HttpWebHandlerAdapter) → WebFilter chain → DispatcherHandler → HandlerMapping/Adapter/ResultHandler → controller. Everything returns `Mono<Void>`; exceptions become error signals caught by the exception handlers. On a servlet container the same `HttpHandler` is bridged by `ServletHttpHandlerAdapter`. This layering is why WebFlux is server-neutral and why Spring Security is a `WebFilter`, not a servlet filter.
code
java · 23 lines// A WebFilter sits between the server and DispatcherHandler.
@Component
@Order(-100) // runs early, before business filters
class TimingWebFilter implements WebFilter {
private static final Logger log = LoggerFactory.getLogger(TimingWebFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
long start = System.nanoTime();
// chain.filter(...) eventually reaches DispatcherHandler.handle(exchange)
return chain.filter(exchange)
.doFinally(signal -> {
long ms = (System.nanoTime() - start) / 1_000_000;
log.info("{} {} -> {} in {}ms",
exchange.getRequest().getMethod(),
exchange.getRequest().getPath(),
exchange.getResponse().getStatusCode(), ms);
});
}
}
// Manual assembly (what Boot does for you):
// HttpHandler handler = WebHttpHandlerBuilder.applicationContext(ctx).build();go deeper
Know filters run before the controller and DispatcherHandler is at the end.
Order the layers and name HttpHandler + WebFilter roles.
Explain HttpWebHandlerAdapter creating the exchange, WebHttpHandlerBuilder assembly, and Security-as-WebFilter implications.
Reason about server-neutral seams, ordering/short-circuit semantics, commit lifecycle, and where cross-cutting concerns belong.
WebFlux is built as a **stack of increasingly higher-level contracts**, each reactive. Knowing the exact order and who creates what is a strong senior signal. **Layer 0 — the reactive runtime.** By default **Reactor Netty**; alternatives are Undertow, Jetty (reactive), or a Servlet 3.1+ container. The runtime accepts the raw connection and produces server-native request/response objects. **Layer 1 — `HttpHandler`.** `org.springframework.http.server.reactive.HttpHandler` is the single, minimal, *server-agnostic* contract: `Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response)`. Each server has an adapter that bridges native I/O to this: `ReactorHttpHandlerAdapter` (Netty), `ServletHttpHandlerAdapter` (servlet containers), `UndertowHttpHandlerAdapter`, etc. This is the seam that makes WebFlux server-neutral. **Layer 2 — `HttpWebHandlerAdapter`.** This is the `HttpHandler` implementation Spring uses in practice. Its job: create the **`ServerWebExchange`** from the request/response, apply the `WebSessionManager`, `LocaleContextResolver`, `ServerCodecConfigurer`, `ForwardedHeaderTransformer`, and then delegate to the `WebHandler` chain. It also applies the exception handlers and logs. **Layer 3 — `WebFilter` chain.** `WebFilter.filter(ServerWebExchange, WebFilterChain)` — the reactive analogue of a servlet `Filter`. This is where cross-cutting concerns live: **Spring Security** (`SecurityWebFilterChain`/`WebFilterChainProxy`), CORS, request logging, tracing/observability, metrics. Filters are ordered and can short-circuit (e.g., auth returning 401 without calling the chain). They wrap the downstream `Mono<Void>`. **Layer 4 — `WebExceptionHandler`.** `handle(exchange, Throwable)` — catches errors bubbling up as reactive error signals from anywhere downstream. Spring Boot registers `DefaultErrorWebExceptionHandler` (renders the error page/JSON); `ResponseStatusExceptionHandler` maps `ResponseStatusException`/`@ResponseStatus` to status codes. **Layer 5 — the target `WebHandler` = `DispatcherHandler`.** At the end of the filter chain sits the delegate `WebHandler`, which is the `DispatcherHandler`. From here the pipeline described earlier runs: `HandlerMapping` → `HandlerAdapter` → controller method → `HandlerResultHandler` → response. **Assembly.** `WebHttpHandlerBuilder.applicationContext(ctx).build()` wires all of the above by discovering beans: the `WebHandler` bean named `webHandler` (the DispatcherHandler), all `WebFilter` beans (ordered), all `WebExceptionHandler` beans, the `WebSessionManager`, `ServerCodecConfigurer`, and `LocaleContextResolver`. Spring Boot's `WebFluxAutoConfiguration` + `HttpHandlerAutoConfiguration` do this for you and expose the resulting `HttpHandler` bean the server binds to. **Full trace of one GET request:** 1. Netty receives bytes → `ReactorHttpHandlerAdapter` builds `ServerHttpRequest`/`ServerHttpResponse` and calls the `HttpHandler`. 2. `HttpWebHandlerAdapter.handle` creates the `ServerWebExchange` and calls the first `WebFilter`. 3. Filters run in order (e.g., Security authenticates, sets `getPrincipal`); the chain eventually invokes `DispatcherHandler.handle(exchange)`. 4. DispatcherHandler: `HandlerMapping.getHandler` → `RequestMappingHandlerAdapter.handle` (resolves args, invokes controller) → `Mono<HandlerResult>`. 5. A `HandlerResultHandler` (e.g., `ResponseBodyResultHandler`) serializes the return value into `exchange.getResponse()` via `HttpMessageWriter`s. 6. The `Mono<Void>` completes; if any stage errored, a `WebExceptionHandler` rendered the error instead. **Gotchas / senior points.** - Spring Security in WebFlux is a `WebFilter`, so it protects *all* WebFlux handlers (annotated + functional) uniformly — unlike MVC's servlet-filter model. - Because filters wrap the *whole* downstream `Mono`, `doFinally`/`then(...)` in a filter is the right place for timing/cleanup, and it runs after the response commits. - `beforeCommit` on the response is the last safe point to add headers; after commit, headers are frozen. - The exchange threads through unchanged unless a filter calls `mutate()`; downstream sees the mutated copy only if the mutated exchange is passed to `chain.filter(...)`. - Ordering: `@Order`/`Ordered` on filters is critical (security must precede business filters).
- Why is HttpHandler the layer that makes WebFlux server-agnostic?It's a minimal contract (Mono<Void> handle(request, response)) with no servlet dependency; each server provides a thin adapter to it, so all higher layers (filters, DispatcherHandler) are unaware of the underlying server.
- How does Spring Security integrate into this chain, and why does that matter?As a WebFilter (SecurityWebFilterChain via WebFilterChainProxy) that runs before DispatcherHandler, so it uniformly guards both annotated controllers and functional routes — there's no separate servlet-filter layer.
- Where would you add a response header safely, and why not later?In a filter before the response commits, or via response.beforeCommit(...); after the response is committed the status/headers are flushed and become immutable.
saying these in an interview costs you the question
- Placing DispatcherHandler before WebFilters
- Saying DispatcherHandler creates the exchange
- Assuming servlet Filters/Security apply to WebFlux
- Thinking HttpHandler depends on the servlet API