skip to content

When several routes could match a request, how does the gateway decide which one wins, and how is route order controlled?

level: principalimportance: should knowfreq 35%

answer

  1. RoutePredicateHandlerMapping = first match wins
  2. sorted by route.order, lower = higher priority
  3. unset order -> declaration order
  4. specific before catch-all (/** shadows)
  5. /actuator/gateway/routes to debug

basics

~20 s

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

At 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 lines
yaml
spring:
  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

for a junior

Likely unaware ordering matters.

for a middle

Knows first match wins and to put specific routes first.

for a senior

Explains the order field semantics and shadowing, uses the actuator endpoint to debug.

for a principal

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

context