How do you compose RequestPredicates with and(), or(), and negate(), and what does each combinator mean semantically?
answer
- and = both, or = either, negate = not
- left-associative chaining
- short-circuit like boolean logic
- and() merges path-variable state
- GET = method.and(path)
basics
~10 sRequestPredicate has and(), or(), and negate(). and() requires both predicates to match, or() requires at least one, negate() inverts. You chain them to build precise route conditions like GET("/x").and(accept(APPLICATION_JSON)).
solid answer
~40 s`RequestPredicate` exposes three combinators. `and(other)` returns a predicate that matches only if **both** match — used to narrow a route, e.g. `GET("/users").and(accept(APPLICATION_JSON))`. `or(other)` matches if **either** matches — used to accept alternatives, e.g. `contentType(APPLICATION_JSON).or(contentType(APPLICATION_XML))`. `negate()` inverts a predicate. They compose left-to-right and, like Java boolean logic, short-circuit: `and` stops at the first false, `or` stops at the first true. Precedence is explicit through chaining — `a.and(b).or(c)` means `(a AND b) OR c`. A subtle point: `and()` also merges matching *state* — for instance a path match on the left exposes captured path variables to the request, which nested routing relies on. Composition keeps routes declarative and readable versus one giant custom predicate.
code
java · 10 linesimport static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.http.MediaType.*;
RequestPredicate jsonOrder =
GET("/orders/{id}")
.and(accept(APPLICATION_JSON))
.and(queryParam("expand", "true").or(queryParam("full", "true")));
// matches: GET /orders/42, Accept: application/json, with ?expand=true OR ?full=true
RouterFunction<ServerResponse> r = route(jsonOrder, handler::getOrder);go deeper
Know and = both must match, or = either matches.
Explain left-associative chaining and short-circuit evaluation.
Explain how and() with path() merges captured path-variable state.
Relate composition to nested routing and prefix consumption; advise positive predicates over negate.
**The combinators.** `RequestPredicate` (in `org.springframework.web.reactive.function.server`) defines three default methods for composition: - `RequestPredicate and(RequestPredicate other)` — logical AND. The composed predicate matches iff **both** operands match. - `RequestPredicate or(RequestPredicate other)` — logical OR. Matches iff **at least one** matches. - `RequestPredicate negate()` — logical NOT. Inverts the result. **Why compose.** The factory methods each test one dimension (method, path, header, query param). Real routes need several dimensions at once, so you AND them: `GET("/orders").and(accept(MediaType.APPLICATION_JSON)).and(queryParam("status", "open"))`. You OR when several inputs are acceptable: `accept(APPLICATION_JSON).or(accept(APPLICATION_XML))`. **Short-circuit + evaluation order.** Because these build on boolean logic, `and` short-circuits on the first `false` and `or` on the first `true`. Chaining is strictly left-associative: `a.and(b).or(c)` is `(a AND b) OR c`, and `a.or(b).and(c)` is `(a OR b) AND c`. If you need different grouping, nest explicitly: `a.and(b.or(c))`. **State merging — the non-obvious part.** Predicates don't just return true/false; a successful match can attach information to the `ServerRequest` (most importantly captured **path variables** and the `matchingPattern` attribute). When you `and()` a `path(...)` predicate, its captured variables become visible to the handler. This is exactly what makes **nested routing** work: `RouterFunctions.nest(path("/users"), builder -> ...)` matches and *consumes* the `/users` prefix, and inner routes match against the remainder while inheriting the captured variables. **GET revisited.** `GET("/x")` == `method(GET).and(path("/x"))` — a concrete, everyday use of `and()`. **Gotchas.** - Over-broad `or()` can accidentally match requests you meant to exclude. - `negate()` on a path predicate rarely does what people expect (it doesn't strip/consume path), so prefer positive predicates. - Order among *sibling routes* (first-match-wins) is separate from `and/or` *within* one predicate — don't conflate the two. **When to use.** Prefer composed built-in predicates over a hand-written `RequestPredicate` implementation; they're readable, self-documenting, and interoperate with nested routing and path-variable capture.
- How is a.and(b).or(c) grouped?Left-associatively: (a AND b) OR c. For (b OR c) you must nest: a.and(b.or(c)).
- Does negate() on a path predicate strip the matched prefix?No. negate() only inverts the boolean; it doesn't consume path segments the way a positive path predicate does in nested routing.
saying these in an interview costs you the question
- Assuming or() has higher precedence than and()
- Thinking and()/or() run in parallel rather than short-circuiting
- Believing negate() rewrites the path