skip to content

What do nest() and path() do in a RouterFunctions builder, and when would you use them?

level: middleimportance: should knowfreq 35%

answer

  1. nest = factor out shared predicate (ANDed)
  2. path(...) = nest(RequestPredicates.path(...))
  3. group by prefix or Accept/contentType
  4. filter inside nest = group-scoped
  5. still first-match-wins order

basics

~10 s

nest() groups several routes under a shared RequestPredicate (like a common path prefix or Accept header) so you don't repeat it on every route. path("/x", ...) is shorthand for nest() with a path predicate.

solid answer

~40 s

nest() factors a common RequestPredicate out of a group of nested routes: you write the shared predicate once and the inner routes only specify what differs. It's most often used for a shared path prefix and/or content-negotiation headers. path("/persons", builder -> ...) is convenience for nest(RequestPredicates.path("/persons"), ...). Inside the nested consumer you keep building routes whose predicates are ANDed with the outer one, and you can nest further. This removes duplication (no repeating "/persons" on every method), keeps related routes visually grouped, and lets you attach a filter to just that group. The result still compiles to a single RouterFunction after .build(). Matching remains order-sensitive within and across nests.

code

java · 18 lines
java
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import org.springframework.web.reactive.function.server.*;

RouterFunction<ServerResponse> routes(PersonHandler h) {
    return RouterFunctions.route()
        // path() == nest(path(...)); accept nest shares content negotiation
        .path("/persons", b1 -> b1
            .nest(accept(APPLICATION_JSON), b2 -> b2
                .GET("/{id}", h::getById)   // GET /persons/{id}, Accept: application/json
                .GET(h::list))              // GET /persons
            .POST(h::create)                // POST /persons (no Accept constraint)
            .filter((req, next) -> {        // filter scoped to /persons only
                // e.g. auth/logging for this group
                return next.handle(req);
            }))
        .build();
}

go deeper

for a junior

Recognizes nest()/path() reduce repetition of a shared prefix.

for a middle

Uses nest() for shared path and header predicates and scopes filters to a group.

for a senior

Reasons about predicate ANDing, nested path variables, ordering/shadowing, and short-circuit evaluation.

for a principal

Designs a clean, hierarchical route table and standardizes group-scoped cross-cutting concerns.

**The problem nest() solves:** without it, every route repeats shared predicate fragments — the same base path, the same `accept(APPLICATION_JSON)`, the same header check. That's noisy and error-prone. **`nest(RequestPredicate predicate, Consumer<Builder> routes)`** on the `RouterFunctions.Builder` opens a nested scope. Every route you register inside the consumer has its predicate **logically ANDed** with the outer `predicate`. So: ```java route() .nest(path("/persons"), b -> b .GET("/{id}", h::getById) // matches GET /persons/{id} .GET(h::list) // matches GET /persons .POST(h::create)) // matches POST /persons .build(); ``` The `/persons` prefix is declared once. Inner GET path `/{id}` is appended to it. **`path(String pattern, Consumer<Builder>)`** is pure sugar for `nest(RequestPredicates.path(pattern), ...)` — the overwhelmingly common case of nesting by URL prefix. There is also a `path(String, HandlerFunction)` single-route overload. **Nesting by non-path predicates.** `nest` takes any `RequestPredicate`, so you can group by content negotiation, e.g. share `accept(APPLICATION_JSON)` or `contentType(APPLICATION_JSON)` across a set of routes, or nest by header. You can combine: `nest(path("/api").and(accept(APPLICATION_JSON)), ...)`. **Deep nesting.** Nests can contain nests, mirroring a resource hierarchy — e.g. `/persons` outer, then a `/{id}/orders` inner nest — each level ANDs onto the accumulated predicate. **Attaching filters to a group.** Because a nest defines a scope, calling `.filter(...)`, `.before(...)`, `.after(...)`, or `.onError(...)` inside the consumer applies only to routes in that nest — handy for group-scoped auth or logging without affecting sibling routes. **Predicate composition primitives.** `RequestPredicate` itself supports `.and(other)`, `.or(other)`, `.negate()`. nest() is essentially a structural way to apply `.and()` to many routes at once while also improving readability and enabling nested path variable extraction. **Gotchas:** - Matching is still **first-match-wins in declaration order**. Put `/{id}` after any literal sibling that could otherwise be shadowed (e.g. a literal `/search` should precede `/{id}` if `/{id}` would capture 'search'). - Path variables from the **outer** nest are available to inner handlers (e.g. nesting `/persons/{pid}` then `/orders/{oid}` — both are readable via `pathVariable`). - A nest with a non-matching outer predicate short-circuits: none of its inner routes are even evaluated, which is a small performance win for large route tables. - `path()` uses `PathPattern` matching (same engine as annotated mappings), so `{var}` and `**` behave consistently. **When to use:** whenever two or more routes share a prefix or header predicate, or when you want to scope a filter to a subset of routes. For a single route, just use `.GET/.POST` directly.

  • What is path("/api", ...) equivalent to?
    nest(RequestPredicates.path("/api"), ...) — it's convenience sugar for nesting by a path-prefix predicate.
  • Can a filter apply to only some routes?
    Yes — call .filter()/.before()/.after()/.onError() inside a nest() consumer and it applies only to the routes within that nest.

saying these in an interview costs you the question

  • Thinking nest() ORs predicates — it ANDs the outer predicate onto inner routes.
  • Assuming route order stops mattering inside a nest.
  • Believing path() and nest() are unrelated rather than sugar/general-case.
  • Expecting a filter inside a nest to affect sibling routes outside it.

context