skip to content

Explain the roles of HandlerMapping, HandlerAdapter, and HandlerResultHandler and how DispatcherHandler chains them.

level: middleimportance: must knowfreq 60%

answer

  1. Find / Invoke / Render
  2. getHandler -> supports+handle -> supports+handleResult
  3. Triad per handler style
  4. Ordered lists, first supporter wins
  5. No result handler -> 500 not 404

basics

~10 s

HandlerMapping finds which handler matches the request. HandlerAdapter knows how to call that handler and returns a HandlerResult. HandlerResultHandler takes that result and writes the HTTP response. DispatcherHandler runs them in that order.

solid answer

~40 s

These three strategy interfaces decouple *finding*, *invoking*, and *rendering*. **HandlerMapping** (`getHandler(exchange)`) resolves the request to a handler object — `RequestMappingHandlerMapping` for annotated methods, `RouterFunctionMapping` for functional routes. **HandlerAdapter** (`supports(handler)` + `handle(exchange, handler)`) abstracts *how* to invoke a handler of that type, returning `Mono<HandlerResult>`; `RequestMappingHandlerAdapter` resolves method arguments, invokes the method, wraps the return value. **HandlerResultHandler** (`supports(result)` + `handleResult(exchange, result)`) writes the response — `ResponseBodyResultHandler` serializes `@ResponseBody` via `HttpMessageWriter`, `ViewResolutionResultHandler` renders views, `ResponseEntityResultHandler` handles `ResponseEntity`. DispatcherHandler iterates each ordered list and picks the first supporter. This is the strategy pattern: adding a new handler *style* means adding a mapping+adapter+result-handler triad, not editing DispatcherHandler. All are beans from the context.

code

java · 31 lines
java
// Sketch of the three contracts DispatcherHandler relies on
public interface HandlerMapping {
    Mono<Object> getHandler(ServerWebExchange exchange);
}

public interface HandlerAdapter {
    boolean supports(Object handler);
    Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler);
}

public interface HandlerResultHandler {
    boolean supports(HandlerResult result);
    Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result);
}

// Custom result handler placed before ResponseBodyResultHandler via @Order
@Component
@Order(0)
class CsvResultHandler implements HandlerResultHandler {
    public boolean supports(HandlerResult result) {
        return result.getReturnType().getGenericParameterType()
                     .getTypeName().contains("CsvReport");
    }
    public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
        CsvReport report = (CsvReport) result.getReturnValue();
        byte[] bytes = report.render().getBytes(StandardCharsets.UTF_8);
        var buffer = exchange.getResponse().bufferFactory().wrap(bytes);
        exchange.getResponse().getHeaders().setContentType(new MediaType("text", "csv"));
        return exchange.getResponse().writeWith(Mono.just(buffer));
    }
}

go deeper

for a junior

Match each interface to find/invoke/render in one line each.

for a middle

State the exact method contracts and give a concrete implementation for each.

for a senior

Explain ordering, the first-supporter algorithm, and where reading vs writing of bodies lives.

for a principal

Discuss extensibility as strategy-pattern triads and how customization hooks (WebFluxConfigurer, codecs, argument resolvers) map onto the three stages.

DispatcherHandler is deliberately thin — the real work is delegated to three ordered lists of strategy beans. Understanding the *contract* of each is the key middle-level insight. **1. `HandlerMapping` — request → handler.** - Contract: `Mono<Object> getHandler(ServerWebExchange exchange)`. - Returns the handler object (could be a `HandlerMethod`, a `HandlerFunction`, a resource handler, etc.), or empty if it doesn't match. - Key implementations: `RequestMappingHandlerMapping` (annotation model — matches URL, method, headers, params, content type against `@RequestMapping` metadata), `RouterFunctionMapping` (functional model), `SimpleUrlHandlerMapping`/`ResourceUrlProvider` for static resources. - Also produces CORS config and matched-pattern attributes stored on the exchange. **2. `HandlerAdapter` — invoke handler → HandlerResult.** - Contract: `boolean supports(Object handler)` and `Mono<HandlerResult> handle(ServerWebExchange, Object handler)`. - The adapter abstracts the *calling convention*. `RequestMappingHandlerAdapter` uses `HandlerMethodArgumentResolver`s to bind arguments (`@RequestBody`, `@PathVariable`, `ServerWebExchange`, `Mono<T>`, etc.), invokes the method reactively, and wraps the return value + a `MethodParameter` (return-type info) into a `HandlerResult`. `HandlerFunctionAdapter` invokes a `HandlerFunction` returning `Mono<ServerResponse>`. `SimpleHandlerAdapter` invokes a raw `WebHandler`. **3. `HandlerResultHandler` — HandlerResult → response.** - Contract: `boolean supports(HandlerResult result)` and `Mono<Void> handleResult(ServerWebExchange, HandlerResult result)`. - Decides *how the return value becomes bytes*. `ResponseBodyResultHandler` (return value serialized via `HttpMessageWriter`s — Jackson for JSON, etc., honoring content negotiation). `ResponseEntityResultHandler` (unwraps status/headers/body of `ResponseEntity`). `ViewResolutionResultHandler` (String view name / model → template rendering). `ServerResponseResultHandler` (functional `ServerResponse.writeTo`). **The chaining algorithm** inside `DispatcherHandler.handle`: ``` Mono.fromCallable(getHandlerMappings) .concatMap(mapping -> mapping.getHandler(exchange)) .next() // first matching handler .switchIfEmpty(createNotFoundError()) // 404 .flatMap(handler -> invokeHandler(exchange, handler)) // pick supporting HandlerAdapter .flatMap(result -> handleResult(exchange, result)); // pick supporting HandlerResultHandler ``` `invokeHandler` scans `handlerAdapters` for `supports(handler)`; `handleResult` scans `resultHandlers` for `supports(result)`, throwing `IllegalStateException("No HandlerResultHandler")` if none matches. **Ordering.** Each list is sorted by `@Order`/`Ordered`. This lets you place a more specific strategy first (e.g., a custom result handler before `ResponseBodyResultHandler`). **Gotchas.** - A return type with no supporting result handler → 500 `IllegalStateException`, not 404. - The adapter, not DispatcherHandler, owns argument resolution and message *reading*; result handlers own message *writing*. Mixing these up is a common confusion. - Because everything is `Mono`/`Flux`, exceptions surface as error signals; each stage can be wrapped, and `WebExceptionHandler`s catch them downstream. **When to extend.** To support a brand-new handler style you add a matching triad; to change serialization you customize `HttpMessageWriter`s via `WebFluxConfigurer.configureHttpMessageCodecs`; to add argument types you register `HandlerMethodArgumentResolver`s.

  • Which strategy owns deserializing the @RequestBody, and which owns serializing the response body?
    The HandlerAdapter (via argument resolvers + HttpMessageReaders) reads/deserializes the request body; the HandlerResultHandler (ResponseBodyResultHandler via HttpMessageWriters) serializes the response body.
  • How would you add support for an entirely new kind of handler?
    Register a HandlerMapping to resolve it, a HandlerAdapter whose supports() recognizes it and invokes it into a HandlerResult, and a HandlerResultHandler to render its return type — all as ordered beans.

saying these in an interview costs you the question

  • Saying HandlerMapping invokes the controller method
  • Claiming DispatcherHandler serializes JSON itself
  • Confusing which strategy reads vs writes the body

context