How do you wire handler beans into routes with a RouterFunction, and how does the request reach the right handler method?
answer
- @Bean RouterFunction<ServerResponse>
- RouterFunctions.route().GET(path, handler::method).build()
- RouterFunctionMapping discovers all RouterFunction beans
- predicates evaluated in order — first match wins
- nest/path/filter for grouping + cross-cutting
basics
~10 sDefine a @Bean RouterFunction<ServerResponse> using RouterFunctions.route(), mapping each path+method predicate to a handler method reference (e.g. .GET("/users/{id}", handler::getUser)). Spring uses the router to dispatch each request to the matching handler.
solid answer
~40 sYou register handlers as beans (often a @Component 'handler' class holding several methods), then declare a @Bean RouterFunction<ServerResponse> built with the RouterFunctions.route() builder. Each route pairs a RequestPredicate (path pattern + HTTP method, plus optional Accept/Content-Type/query predicates) with a HandlerFunction reference like handler::getUser. At runtime a RouterFunctionMapping collects all RouterFunction beans; the DispatcherHandler asks the router to match the incoming ServerRequest, and route() evaluates predicates in declaration order, returning the first matching handler's Mono<ServerResponse>. You compose routes with the builder's fluent methods, nest with path(...)/nest(...), share cross-cutting logic with .filter(...) or .before/.after, and can combine multiple RouterFunctions with .and(...). Order matters: the first matching predicate wins, so put specific routes before broad ones.
code
java · 31 linesimport org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.*;
import reactor.core.publisher.Mono;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
@Component
class UserHandler {
Mono<ServerResponse> getUser(ServerRequest r) { /* ... */ return ServerResponse.ok().build(); }
Mono<ServerResponse> listUsers(ServerRequest r) { return ServerResponse.ok().build(); }
Mono<ServerResponse> createUser(ServerRequest r) { return ServerResponse.ok().build(); }
}
@Configuration
class RouterConfig {
@Bean
RouterFunction<ServerResponse> userRoutes(UserHandler h) {
return RouterFunctions.route()
.path("/users", b -> b
.GET("/{id}", accept(MediaType.APPLICATION_JSON), h::getUser)
.GET("", h::listUsers)
.POST("", h::createUser))
.filter((request, next) -> { // functional middleware
// e.g. logging/auth, then delegate
return next.handle(request);
})
.build();
}
}go deeper
Know that routes are declared in a @Bean RouterFunction with route().GET(path, handler::method).build().
Explain RequestPredicates (method/path/accept), path variables via request.pathVariable, and that the router must be a bean.
Describe dispatch: RouterFunctionMapping discovers routers, predicates evaluated in order, first match wins; use nest/filter for structure.
Design route composition across modules, filter-based cross-cutting concerns, ordering/specificity pitfalls, and mixing with annotated controllers.
**The pieces.** 1. *Handler bean* — a class (commonly `@Component`) with methods of shape `Mono<ServerResponse> m(ServerRequest)`. Keeping them in a bean lets you inject services. 2. *RouterFunction bean* — a `@Bean` returning `RouterFunction<ServerResponse>` that maps predicates to those handler methods. **Building routes.** Use the `RouterFunctions.route()` builder (the modern fluent API): ```java @Bean RouterFunction<ServerResponse> userRoutes(UserHandler h) { return RouterFunctions.route() .GET("/users/{id}", h::getUser) .GET("/users", h::listUsers) .POST("/users", h::createUser) .DELETE("/users/{id}", h::deleteUser) .build(); } ``` Each `.GET/.POST/...` takes a path pattern (Spring `PathPattern` syntax, incl. `{var}` path variables) and a `HandlerFunction`. Overloads accept an extra `RequestPredicate` (e.g. `accept(APPLICATION_JSON)`, `contentType(...)`, `queryParam(...)`) so you can route by more than method+path. There is also the older static style: `RouterFunctions.route(RequestPredicates.GET("/x"), handler::method)` composed with `.andRoute(...)`. **How dispatch works.** - On startup, `RouterFunctionMapping` (a `HandlerMapping`) discovers **all** `RouterFunction` beans in the context and combines them. - For each request, WebFlux's `DispatcherHandler` consults the `HandlerMapping`s. `RouterFunctionMapping` calls `routerFunction.route(serverRequest)`, which returns a `Mono<HandlerFunction>` — present if some predicate matched. - `route()` evaluates its registered predicates **in declaration order** and returns the **first** match. `HandlerFunctionAdapter` then invokes the handler and writes the resulting `ServerResponse`. **Composition & cross-cutting concerns.** - `.filter(HandlerFilterFunction)` wraps handlers (auth, logging, error mapping) — like functional middleware. - `.before(...)` / `.after(...)` transform the request/response. - `.path("/api", builder -> ...)` or `.nest(predicate, ...)` group routes under a common prefix/predicate. - Multiple `RouterFunction` beans coexist; you can also `.and(otherRouter)` to combine explicitly. **Gotchas.** - **Order matters**: first matching predicate wins. A broad route like `GET("/users/{id}")` placed before `GET("/users/me")` will shadow the specific one (though PathPattern specificity within a single router helps, cross-router or ambiguous cases still bite). Put specific routes first. - The RouterFunction must be a **bean** — a route function you build but never register won't be picked up. - Handler *methods are not endpoints by themselves* — without a route mapping them, they're just methods; there's no component scan of endpoints like `@GetMapping`. - Path variables are read in the handler via `request.pathVariable("id")`, query params via `request.queryParam("q")` — the router extracts them but the handler pulls them out. - Reactive vs servlet: import from `org.springframework.web.reactive.function.server` for WebFlux (returns reactive types); the `web.servlet.function` package is the MVC twin.
- If two routes could match the same request, which one handles it?The first matching predicate in declaration order wins — route() short-circuits on the first match. So specific routes should be declared before broad/catch-all ones.
- How do you apply authentication or logging to a group of functional routes?Use a HandlerFilterFunction via the builder's .filter(...) (or .before/.after), optionally scoped to a nested group with .nest/.path. The filter wraps the matched handler like middleware, calling next.handle(request) to delegate.
- What component actually discovers RouterFunction beans and dispatches to them?RouterFunctionMapping — a HandlerMapping that collects all RouterFunction beans; the DispatcherHandler uses it to match a request, and HandlerFunctionAdapter invokes the resulting handler.
saying these in an interview costs you the question
- Building a RouterFunction but not exposing it as a @Bean
- Expecting handler methods to be auto-registered like @GetMapping
- Ignoring predicate ordering so a broad route shadows a specific one
- Thinking each route needs its own RouterFunctionMapping (one mapping collects all router beans)