How does the path()/GET() RequestPredicate match URLs, and what path-pattern syntax and matching-order pitfalls should you know?
answer
- PathPattern engine, not AntPathMatcher
- {var} one segment, * one segment, ** trailing many
- {id:\\d+} regex, {*rest} capture remainder
- first-match-wins: specific before general
- nest() consumes the prefix
basics
~20 spath()/GET() match the request path using a PathPattern. Syntax: {var} captures a path variable, * matches one segment, ** matches multiple. Because the first matching route wins, declare specific routes before broad ones so they aren't shadowed.
solid answer
~40 s`RequestPredicates.path(String)` (and `GET`/`POST` which wrap it) compile the pattern into a `PathPattern` from `org.springframework.web.util.pattern`. Syntax: `{name}` captures a single segment as a path variable (readable via `request.pathVariable("name")`); `{name:regex}` adds a regex constraint; `*` matches exactly one segment; `**` matches zero-or-more trailing segments; `{*name}` captures the remaining path. On a successful match the predicate stores captured variables and the matching pattern on the `ServerRequest`, which is how handlers and nested routes read them. The big operational gotcha is that routes are tested in **declaration order and first-match wins** — so `GET("/users/**")` declared before `GET("/users/me")` will swallow `/users/me`. Order specific-to-general. In nested routing, `nest(path("/api"), ...)` matches and *consumes* the prefix so inner patterns match the remainder.
code
java · 12 linesimport static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.*;
RouterFunction<ServerResponse> routes(Handler h) {
return nest(path("/users"),
route(GET("/me"), h::currentUser) // specific FIRST
.andRoute(GET("/{id:\\d+}"), h::byId) // numeric id
.andRoute(GET("/**"), h::catchAll)); // catch-all LAST
}
// GET /users/me -> currentUser (would be shadowed if /** came first)
// GET /users/42 -> byId, pathVariable("id") == "42"
// GET /users/a/b/c -> catchAllgo deeper
Know {var}, *, ** and that pathVariable() reads captures.
Explain first-match-wins ordering and specific-before-general.
Detail PathPattern syntax including {id:regex}, {*rest}, and nest() prefix consumption.
Discuss no-most-specific-scoring trade-off, trailing-slash policy change, and route-tree design with nest.
**PathPattern engine.** WebFlux parses request paths into a `PathContainer` and matches them with `PathPattern` (package `org.springframework.web.util.pattern`), the same parsed-path engine used by annotated controllers in WebFlux. It's stricter and faster than the old `AntPathMatcher` and is the default (and only) matcher in WebFlux. **Pattern syntax.** - `{name}` — capture exactly one path segment as variable `name`. - `{name:regex}` — capture one segment only if it matches the regex, e.g. `{id:\\d+}`. - `*` — match exactly one segment (any value). - `**` — match zero or more trailing segments; only allowed at the **end** of the pattern. - `{*name}` — capture the remaining segments (including slashes) into `name`; also end-only. - Literal segments match exactly. **Reading captures.** After `path(...)`/`GET(...)` matches, captured variables are exposed via `ServerRequest.pathVariable(String)` / `pathVariables()`. The matched pattern is available as a request attribute (`RouterFunctions.MATCHING_PATTERN_ATTRIBUTE`). This state is set as a side effect of the predicate matching, which is exactly why composing with `and()` on a path matters. **First-match-wins.** A `RouterFunction` composes routes with `andRoute`/`add`, and routing evaluates them **in declaration order, returning the first match**. Consequences: - Declare **specific before general**: `GET("/users/me")` before `GET("/users/{id}")` before `GET("/users/**")`. - A `**` or `{*rest}` catch-all near the top shadows everything below it. - Unlike some frameworks, there is **no automatic most-specific-wins scoring across sibling routes** in the functional model — you control precedence by ordering. **Nested routing and prefix consumption.** `RouterFunctions.nest(path("/api"), builder -> builder.GET("/users", handler))` matches `/api`, **consumes** that prefix, and evaluates inner routes against the remaining `/users`. Captured variables from the outer pattern remain visible inside. This keeps large route trees DRY. **Trailing slash.** Modern Spring (6.x) no longer matches trailing slashes by default (`setUseTrailingSlashMatch` was deprecated/removed); `/users` and `/users/` are distinct unless you add explicit handling. Don't assume lenient trailing-slash behavior. **Gotchas.** - Putting `**`/catch-all high in the list. - Expecting `*` to cross `/` boundaries — it matches a single segment only; use `**`. - Forgetting regex escaping in Java string literals (`\\d+`). - Assuming most-specific-wins; it's declaration-order-wins. **When to use.** Use precise patterns and order them deliberately; use `nest` to factor common prefixes; reserve `**`/`{*rest}` for genuine catch-alls placed last.
- Why must GET("/users/me") be declared before GET("/users/{id}")?Routing is first-match-wins in declaration order; {id} matches the segment 'me' too, so if it came first it would shadow the /me route.
- What's the difference between * and ** in a path pattern?* matches exactly one path segment and does not cross '/'; ** matches zero or more trailing segments and is only valid at the end of the pattern.
saying these in an interview costs you the question
- Claiming the router picks the most-specific pattern automatically
- Thinking * matches across multiple segments
- Assuming trailing slashes still match by default in Spring 6
- Believing WebFlux uses AntPathMatcher