skip to content

What is a RouterFunction in Spring WebFlux, and how do you define one route with RouterFunctions.route()?

level: juniorimportance: must knowfreq 55%

answer

  1. predicate + handler = route
  2. RouterFunctions.route()...build()
  3. HandlerFunction: ServerRequest -> Mono<ServerResponse>
  4. @Bean RouterFunction<ServerResponse>
  5. first match wins

basics

~10 s

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

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

for a junior

Know it's an annotation-free way to map request->handler; can write a basic GET route with route().GET(...).build().

for a middle

Understands predicate/handler split, ServerRequest/ServerResponse builders, and @Bean registration.

for a senior

Can articulate reactive return types, coexistence with controllers, and testing via bindToRouterFunction.

for a principal

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.

context