What is a RouterFunction in Spring WebFlux, and how do you define one route with RouterFunctions.route()?
answer
- predicate + handler = route
- RouterFunctions.route()...build()
- HandlerFunction: ServerRequest -> Mono<ServerResponse>
- @Bean RouterFunction<ServerResponse>
- first match wins
basics
~10 sA RouterFunction is code (not annotations) that maps an incoming request to a handler. You build it with RouterFunctions.route(), e.g. route().GET("/hello", req -> ServerResponse.ok().bodyValue("hi")).build().
solid answer
~30 sIn WebFlux, functional routing is an alternative to @RestController/@GetMapping. A RouterFunction<ServerResponse> pairs a RequestPredicate (which requests match — method, path, headers) with a HandlerFunction (what to do). You create it with the fluent builder RouterFunctions.route(), chaining methods like .GET(path, handler), .POST(path, handler), then .build(). Each handler takes a ServerRequest and returns a Mono<ServerResponse>. You expose the whole thing as a @Bean of type RouterFunction<ServerResponse> and Spring wires it into the dispatch chain automatically. Everything is plain Java/Kotlin, so routes are defined imperatively and are fully testable without a running server via RouterFunctions and WebTestClient.bindToRouterFunction.
code
java · 21 linesimport static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import org.springframework.web.reactive.function.server.*;
import reactor.core.publisher.Mono;
import org.springframework.context.annotation.*;
@Configuration
public class GreetingRouter {
@Bean
public RouterFunction<ServerResponse> routes() {
return RouterFunctions.route()
.GET("/hello/{name}", accept(APPLICATION_JSON), this::hello)
.build();
}
private Mono<ServerResponse> hello(ServerRequest request) {
String name = request.pathVariable("name");
return ServerResponse.ok().bodyValue("Hello, " + name);
}
}go deeper
Know it's an annotation-free way to map request->handler; can write a basic GET route with route().GET(...).build().
Understands predicate/handler split, ServerRequest/ServerResponse builders, and @Bean registration.
Can articulate reactive return types, coexistence with controllers, and testing via bindToRouterFunction.
Frames functional endpoints as a deliberate architectural choice; reasons about dispatch integration and composition.
**Functional endpoints** are Spring WebFlux's second programming model for building web endpoints, living in package `org.springframework.web.reactive.function.server`. Instead of declaring endpoints with annotations (`@RestController`, `@GetMapping`), you wire requests to handlers with plain code. **The two building blocks:** - **`RequestPredicate`** — a boolean test over an incoming `ServerRequest`: does the HTTP method, path, `Accept` header, content type, or query match? Static factories live in `RequestPredicates` (e.g. `RequestPredicates.GET("/x")`, `accept(APPLICATION_JSON)`, `path("/api")`). - **`HandlerFunction<ServerResponse>`** — a functional interface with one method `Mono<ServerResponse> handle(ServerRequest request)`. It reads the request and produces a response reactively. **`RouterFunction<ServerResponse>`** ties a predicate to a handler: 'if this predicate matches, invoke this handler.' Its core method is `Mono<HandlerFunction<T>> route(ServerRequest)` — it returns the matching handler wrapped in a `Mono`, or empty if nothing matched. **Building routes — two styles:** 1. **Static pair:** `RouterFunctions.route(RequestPredicate, HandlerFunction)` builds a single route; combine with `.andRoute(...)`. 2. **Fluent builder (preferred):** `RouterFunctions.route()` returns a `RouterFunctions.Builder`. Chain `.GET`, `.POST`, `.PUT`, `.DELETE`, `.path`, `.nest`, `.filter`, then call `.build()` to get the `RouterFunction`. **`ServerRequest`** exposes the request functionally: `request.pathVariable("id")`, `request.queryParam("q")`, `request.bodyToMono(Foo.class)`, `request.headers()`. **`ServerResponse`** is a builder: `ServerResponse.ok().bodyValue(obj)`, `.body(fluxOfItems, Item.class)`, `.status(HttpStatus.CREATED).build()` — every terminal call returns `Mono<ServerResponse>` (WebFlux is reactive). **Registration:** expose a `@Bean` whose type is `RouterFunction<ServerResponse>`. The auto-configured `RouterFunctionMapping` collects all such beans and integrates them into the same `DispatcherHandler` dispatch pipeline that serves annotated controllers, so both models can coexist in one app. **When to use:** functional endpoints shine when you want routing logic in one visible place, prefer immutable/imperative wiring over annotation 'magic', want to compose routes programmatically, or are already writing functional/reactive code. Annotated controllers remain the default for most teams because of familiarity and richer built-in features. **Gotchas:** - Don't confuse WebFlux (`...web.reactive.function.server`, handlers return `Mono<ServerResponse>`) with WebMVC.fn (`...web.servlet.function`, handlers return a plain `ServerResponse`). The APIs look identical but the return types and threading model differ. - Predicate **order matters** — the first matching route wins, so put more specific routes before catch-alls. - Forgetting `.build()` leaves you with a `Builder`, not a `RouterFunction` — it won't compile as a bean of the right type.
- What does a HandlerFunction return in WebFlux versus WebMVC.fn?In WebFlux (org.springframework.web.reactive.function.server) it returns Mono<ServerResponse>; in WebMVC.fn (org.springframework.web.servlet.function) it returns a plain, blocking ServerResponse.
- How does Spring know to use your RouterFunction bean?The auto-configured RouterFunctionMapping detects every RouterFunction<ServerResponse> bean, combines them, and plugs them into the DispatcherHandler dispatch pipeline alongside annotated controllers.
saying these in an interview costs you the question
- Thinking you must choose functional OR annotated for the whole app — they coexist.
- Returning a raw ServerResponse (not Mono<ServerResponse>) in a WebFlux handler.
- Forgetting .build(), leaving a Builder instead of a RouterFunction.
- Believing route order doesn't matter.