skip to content

How does Spring choose between multiple candidate @RequestMapping handlers, and what path pattern features (variables, wildcards) participate in matching?

level: seniorimportance: should knowfreq 40%

answer

  1. RequestMappingInfo bundles all conditions
  2. literal > {var} > /* > /**
  3. More/more-specific predicates = more specific
  4. Equal best match -> IllegalStateException ambiguous
  5. PathPattern default in Boot 3; ** only at end

basics

~20 s

When several mappings match, Spring picks the most specific one using RequestMappingInfo comparison: exact paths beat path variables beat wildcards, and more predicates (method, params, headers, produces) make a mapping more specific. Truly equal matches throw an ambiguous-mapping error.

solid answer

~50 s

Each @RequestMapping becomes a RequestMappingInfo combining path patterns, method, params, headers, consumes, and produces conditions. RequestMappingHandlerMapping finds all matching infos for a request, then sorts them with a specificity comparator and picks the best. For paths, specificity ranks literal segments above single path variables ({id}) above single-segment wildcards (*) above the multi-segment ** wildcard; a pattern with fewer wildcards / URI variables wins. Predicates also count: a handler that additionally constrains produces or params outranks one that doesn't when the client matches. Modern Spring (5.3+, default in Boot 3) uses PathPattern parsing rather than the legacy AntPathMatcher. If two mappings are equally specific and both match, Spring raises IllegalStateException ("Ambiguous handler methods") — usually detectable as a startup or first-request failure. Understanding this prevents shadowing bugs where a broad wildcard swallows requests meant for a specific route.

code

java · 18 lines
java
@RestController
@RequestMapping("/files")
public class FileController {

    @GetMapping("/report.pdf")     // literal - most specific
    public Res exact() { ... }

    @GetMapping("/{name}")         // single URI variable
    public Res byName(@PathVariable String name) { ... }

    @GetMapping("/{id:\\d+}")     // regex var: only numeric ids
    public Res byId(@PathVariable long id) { ... }

    @GetMapping("/**")             // catch-all - least specific
    public Res anything() { ... }
}
// GET /files/report.pdf -> exact(); GET /files/42 -> byId();
// GET /files/readme -> byName(); GET /files/a/b/c -> anything()

go deeper

for a junior

Not expected to detail specificity; may know exact paths beat wildcards.

for a middle

Knows path-variable vs wildcard ordering and that ambiguity is an error.

for a senior

Explains RequestMappingInfo conditions, specificity comparison, and regex-variable disambiguation.

for a principal

Discusses PathPattern vs AntPathMatcher migration, trailing-slash/suffix-match removal, and API-design implications of route shadowing.

**The registry.** At startup `RequestMappingHandlerMapping` scans every `@Controller`/`@RestController`, turning each mapped method into a `RequestMappingInfo` — an immutable bundle of *conditions*: patterns, `RequestMethodsRequestCondition`, `ParamsRequestCondition`, `HeadersRequestCondition`, `ConsumesRequestCondition`, `ProducesRequestCondition`. **Matching a request.** For an incoming request, the handler mapping: 1. Filters to `RequestMappingInfo`s whose every condition matches (path pattern matches URL, method allowed, params/headers present, consumes matches Content-Type, produces matches Accept). 2. Sorts the survivors with `RequestMappingInfo.compareTo` using the request as context. 3. Selects the first (most specific). If the top two compare as *equal*, it throws `IllegalStateException: Ambiguous handler methods mapped ...`. **Path pattern specificity.** Whether using the newer `PathPattern` (Spring 5.3+, the default in Spring Boot 3) or legacy `AntPathMatcher`, the ordering intuition is the same, most-to-least specific: - Literal exact path: `/users/active` - Single URI template variable: `/users/{id}` - Single-segment wildcard: `/users/*` - Multi-segment wildcard: `/users/**` Among equally-typed patterns, fewer URI variables and fewer wildcards win; a longer literal prefix is more specific. `PathPattern` also supports `{*path}` to capture the remaining segments. **Predicates add specificity.** Given the same path, a mapping with more/more-specific conditions beats a barer one when the request satisfies them. E.g. two `/users` GET handlers, one with `produces=application/json`; a client sending `Accept: application/json` gets the JSON one because its `ProducesRequestCondition` is more specific for that request. Media-type conditions are compared by how closely they match the request's Accept/Content-Type. **Ambiguity failures.** If you declare `/users/{id}` and `/users/{name}` on the same verb with no other distinguishing condition, they are equally specific → ambiguous → `IllegalStateException`. The fix is to add a distinguishing predicate (regex in the variable like `/users/{id:\\d+}`, different params, or different produces). **PathPattern vs AntPathMatcher.** `PathPattern` (from spring-web) is faster, pre-parsed, and is now the default; `**` is only allowed at the end of a pattern. Legacy `AntPathMatcher` allowed `**` mid-pattern and did `?`/`*`/`**` glob matching at match time. The `useSuffixPatternMatch`/trailing-slash behaviors changed across versions — notably trailing-slash matching was deprecated and disabled by default in Spring 6, so `/users` no longer implicitly matches `/users/`. **Gotchas.** - A broad `@RequestMapping("/**")` catch-all can shadow specific routes if it's equally or more matchable — order doesn't save you; specificity does. - Trailing-slash: don't rely on `/path` matching `/path/` in Spring 6+. - `{id:\\d+}` regex variables narrow matches and break ambiguity ties. - Suffix pattern matching (`.json` extensions) is removed by default for security reasons. **When it matters.** Designing REST hierarchies, avoiding accidental route shadowing, and debugging `Ambiguous handler methods` or unexpected 404s.

  • You mapped /users/{id} and /users/{name} on the same GET. What happens and how do you fix it?
    They are equally specific, so Spring throws IllegalStateException: Ambiguous handler methods. Fix by distinguishing them, e.g. a regex variable /users/{id:\\d+} for numeric ids, or different params/produces conditions.
  • Does declaration order of handlers decide the winner when several match?
    No. Order is irrelevant; Spring sorts matches by specificity via RequestMappingInfo.compareTo. A more specific pattern or predicate wins regardless of where it is declared.

saying these in an interview costs you the question

  • Believing handler declaration order determines which matches
  • Assuming /** can never shadow specific routes
  • Thinking /users still matches /users/ by default in Spring 6
  • Not knowing equally-specific mappings throw at runtime/startup

context