When several routes could match a request, how does the gateway decide which one wins, and how is route order controlled?
answer
- RoutePredicateHandlerMapping = first match wins
- sorted by route.order, lower = higher priority
- unset order -> declaration order
- specific before catch-all (/** shadows)
- /actuator/gateway/routes to debug
basics
~20 sThe RoutePredicateHandlerMapping evaluates routes in order and picks the first whose combined predicate matches. Routes are sorted by their order field (lower = higher priority); if unset, they keep their declared order. First match wins — later routes aren't tried.
solid answer
~50 sAt request time the RoutePredicateHandlerMapping asks the RouteLocator for all routes and evaluates them as an ordered stream, selecting the first whose AND-combined predicate matches; evaluation then stops (first match wins). Ordering is governed by each Route's order property (an int, lower value = higher priority). If you don't set order, routes are matched in the sequence they're defined — YAML list order or DSL .route() call order — effectively order 0 preserving declaration order. Because the first match wins, you place more specific routes before broader catch-alls; a greedy Path=/** early in the list would shadow everything after it. In YAML you set order per route; in the DSL you call .order(n). This determinism is why predicate design and ordering are a real concern in large route tables — overlapping Path patterns plus the wrong order cause silent mis-routing that no predicate error surfaces.
code
yaml · 16 linesspring:
cloud:
gateway:
routes:
- id: admin # specific -> lower order, evaluated first
uri: lb://admin-service
order: -1
predicates:
- Path=/api/admin/**
- id: api # broader catch-all -> later
uri: lb://api-service
order: 0
predicates:
- Path=/api/**
# Without order:-1 on 'admin', if 'api' were listed first it would
# shadow /api/admin/** (first match wins, not longest-prefix).go deeper
Likely unaware ordering matters.
Knows first match wins and to put specific routes first.
Explains the order field semantics and shadowing, uses the actuator endpoint to debug.
Reasons about route-table governance: ordering across merged YAML/DSL/discovery sources, caching/refresh, and preventing silent mis-routing at scale.
**The matching engine.** Incoming requests hit the `RoutePredicateHandlerMapping` (a Spring WebFlux `HandlerMapping`). It obtains the routes from the (usually `CachingRouteLocator`-wrapped) `RouteLocator`, which yields them as a reactive stream **already sorted by the route's `order`**. It then evaluates each route's `AsyncPredicate<ServerWebExchange>` (the AND-combination of that route's predicate factories) **in order** and returns the **first** route whose predicate matches. Once a match is found, the `FilteringWebHandler` runs that route's filters and proxies to its `uri`; **remaining routes are not evaluated**. This is strict **first-match-wins**, not best-match or longest-prefix. **How order is determined.** - Each `Route` has an **`order`** field (an `int`). **Lower value = higher precedence** (evaluated earlier), consistent with Spring's `Ordered` convention. Negative values are allowed and sort before 0. - If you **don't** set `order`, the value defaults such that routes retain their **declaration order** — YAML list order, or the order of `.route(...)` calls in the DSL. Practically, unspecified routes behave as order 0 and preserve insertion order among ties. - In **YAML**: add `order: <n>` to a route entry. In the **DSL**: `.route(id, r -> r.path(...).uri(...))` then set order via the predicate spec / route options (`.route(...)` builder exposes ordering; you configure it on the route). **Consequences and design rules.** - Put **specific routes before general ones.** A catch-all `Path=/**` (or a lenient `Host`) placed early will **shadow** every more-specific route after it, and nothing errors — requests just go to the wrong place. - Predicates on one route are **AND-ed**; there's no cross-route OR — model alternatives as separate routes (each can point to the same uri) and order them intentionally. - With **service-discovery routing** enabled (`spring.cloud.gateway.discovery.locator.enabled`), auto-generated routes are added too and participate in ordering; explicit routes usually need lower `order` to win over them. - **Refresh/caching:** the `CachingRouteLocator` caches the route list; changing YAML requires `/actuator/gateway/refresh` (or restart) to re-sort and re-evaluate. - **Observability:** the `/actuator/gateway/routes` actuator endpoint lists effective routes with their order and predicates — the go-to tool to debug why the wrong route won. **Common gotchas.** - Assuming **longest/most-specific path wins** — it does **not**; only declared/`order` sequence matters. - Two routes with the **same order** fall back to declaration order among themselves — non-obvious if they're split across YAML and DSL sources merged by a composite locator. - A subtly broad predicate (e.g. `Path=/api/**` above `Path=/api/admin/**`) makes the admin route unreachable; fix by ordering the specific route first or tightening the broad one.
- Does Spring Cloud Gateway use longest-prefix matching like a servlet mapping?No. It's strict first-match-wins over routes sorted by their order field, then declaration order. A broad pattern earlier in the list shadows more specific routes regardless of specificity.
- How would you debug a request going to the wrong downstream service?Hit /actuator/gateway/routes to see effective routes with their order and predicates, check for an over-broad Path/Host predicate ordered before the intended route, and fix by lowering the specific route's order or tightening the broad one.
- Two routes have the same order and both match — which wins?The tie is broken by declaration/insertion order (YAML list order or DSL .route() call sequence), which can be non-obvious when YAML and DSL routes are merged by a composite locator.
saying these in an interview costs you the question
- Claiming the most specific / longest path always wins
- Thinking all matching routes execute (they don't — first match stops evaluation)
- Believing higher order value means higher priority (it's the reverse)
- Assuming cross-route OR exists instead of ordering separate routes