skip to content

What is a HandlerFunction<ServerResponse> in Spring WebFlux, and how does it differ from an annotated @RestController method?

level: juniorimportance: must knowfreq 60%

answer

  1. handle(ServerRequest) -> Mono<ServerResponse>
  2. functional model, not annotations
  3. wired via RouterFunction, not @GetMapping
  4. immutable ServerRequest/ServerResponse
  5. same runtime, different style

basics

~10 s

A 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 s

HandlerFunction<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 lines
java
import 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

for a junior

Know the signature: ServerRequest in, Mono<ServerResponse> out, and that it's the annotation-free alternative to @RestController.

for a middle

Explain the immutability of ServerRequest/ServerResponse and that routing is explicit via RouterFunction.

for a senior

Discuss testability, composition, and that behavior/runtime is identical to annotated controllers — only the authoring style differs.

for a principal

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)

context