What is a HandlerFunction<ServerResponse> in Spring WebFlux, and how does it differ from an annotated @RestController method?
answer
- handle(ServerRequest) -> Mono<ServerResponse>
- functional model, not annotations
- wired via RouterFunction, not @GetMapping
- immutable ServerRequest/ServerResponse
- same runtime, different style
basics
~10 sA HandlerFunction is a function that takes a ServerRequest and returns a Mono<ServerResponse>. It's the functional way to write a WebFlux endpoint, instead of using annotations like @GetMapping on a controller method.
solid answer
~40 sHandlerFunction<ServerResponse> is a functional interface with one method: Mono<ServerResponse> handle(ServerRequest request). It represents a single reactive endpoint as a plain function rather than an annotated method. Instead of @RestController + @GetMapping, you write a handler that reads from ServerRequest and returns a Mono<ServerResponse>, then map it to a path via a RouterFunction. Both run on the same reactive engine and non-blocking runtime; the difference is style. The functional model keeps routing explicit and testable (handlers are just methods you can call directly), avoids annotation/reflection magic, and gives programmatic control over route composition. Annotated controllers are more declarative and familiar. Semantics — non-blocking I/O, Reactor types, the same HTTP contract — are identical.
code
java · 12 linesimport org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
// A HandlerFunction is just: ServerRequest -> Mono<ServerResponse>
public Mono<ServerResponse> hello(ServerRequest request) {
return ServerResponse.ok()
.bodyValue("Hello, " + request.pathVariable("name"));
}
// It can also be written as a lambda typed as HandlerFunction<ServerResponse>:
// HandlerFunction<ServerResponse> h = req -> ServerResponse.ok().bodyValue("hi");go deeper
Know the signature: ServerRequest in, Mono<ServerResponse> out, and that it's the annotation-free alternative to @RestController.
Explain the immutability of ServerRequest/ServerResponse and that routing is explicit via RouterFunction.
Discuss testability, composition, and that behavior/runtime is identical to annotated controllers — only the authoring style differs.
Weigh when a functional style pays off (libraries, programmatic/conditional routing, filters as functions) vs. annotated declarativeness for large teams.
**Context.** Spring WebFlux is the reactive, non-blocking web stack (an alternative to Spring MVC). It offers two programming models: the *annotated* model (`@RestController`, `@GetMapping`, familiar from MVC) and the *functional* model (router + handler functions). This question is about the functional model's core building block. **HandlerFunction.** `HandlerFunction<T extends ServerResponse>` is a `@FunctionalInterface` in `org.springframework.web.reactive.function.server`. Its single method is: ```java Mono<T> handle(ServerRequest request); ``` So a handler receives one `ServerRequest` and returns a `Mono<ServerResponse>` (a reactive publisher that will emit exactly one response, asynchronously). Because it is a functional interface, a handler can be a lambda, a method reference, or a method on a `@Component` bean. **ServerRequest / ServerResponse.** These are *immutable*, WebFlux-specific abstractions (not the Servlet `HttpServletRequest`/`Response`). `ServerRequest` gives access to headers, path/query params, and the body via `bodyToMono`/`bodyToFlux`. `ServerResponse` is built with a fluent builder — `ServerResponse.ok().bodyValue(x)` — and the terminal builder call returns a `Mono<ServerResponse>`. **How it differs from an annotated controller.** - *Wiring*: annotated methods are discovered by reflection over `@RequestMapping` annotations; handler functions are wired explicitly through a `RouterFunction` bean (`RouterFunctions.route().GET("/x", handler::method).build()`). - *Testability*: a handler is a normal method taking a `ServerRequest` — you can construct a `MockServerRequest` and call it directly, no dispatcher needed. - *Composition*: routes are composed programmatically (`.and(...)`, nested routes, filters as ordinary functions), which is powerful for building libraries or conditional routing. - *No behavioral difference*: threading, non-blocking I/O, backpressure, content negotiation, and the resulting HTTP semantics are the same; it is purely a different authoring style. **When to use.** Functional endpoints shine when you want explicit, centralized routing, lightweight endpoints, or fine-grained programmatic control; annotated controllers are usually chosen for familiarity and large declarative APIs. The two can coexist in one application. **Gotchas.** - The return type is `Mono<ServerResponse>` — a *single* response publisher — even when the body itself is a `Flux` of many items (the body is a stream inside one response). - Handlers are typically registered as beans and referenced by method reference; they are not auto-scanned as endpoints the way `@GetMapping` methods are. - This is *reactive* WebFlux (`org.springframework.web.reactive.function.server`). There is a separate `org.springframework.web.servlet.function` variant for MVC that uses the same names but blocking types — don't mix imports.
- Can annotated controllers and functional endpoints coexist in the same WebFlux app?Yes. Both models run on the same reactive infrastructure and can be used side by side; the DispatcherHandler routes to whichever matches. Teams often mix them, though most keep one style per module for consistency.
- Why does handle() return a Mono<ServerResponse> and not just a ServerResponse?Because building the response may itself require async work (e.g. reading the request body, a DB call). Returning a Mono lets the whole pipeline stay non-blocking; the framework subscribes and writes the response when it resolves.
saying these in an interview costs you the question
- Thinking HandlerFunction returns ServerResponse directly instead of Mono<ServerResponse>
- Believing functional endpoints are blocking or non-reactive
- Confusing WebFlux ServerRequest with the Servlet HttpServletRequest
- Assuming handler methods are auto-discovered like @GetMapping (they must be wired via a RouterFunction)