skip to content

How does RequestMappingHandlerMapping build and use its RequestMappingInfo registry to match a request to a controller method?

level: middleimportance: must knowfreq 60%

answer

  1. afterPropertiesSet → scan @Controller beans
  2. method → RequestMappingInfo (path+method+params+headers+consumes+produces)
  3. MappingRegistry stores info→HandlerMethod
  4. getMatchingCondition narrows, comparator sorts
  5. 405/415/406 vs plain 404; equal best = ambiguous

basics

~20 s

At startup it scans all @Controller beans, turns each @RequestMapping method into a RequestMappingInfo (path, HTTP method, params, headers, consumes, produces) and stores them in a registry. Per request it finds the RequestMappingInfos that match, picks the most specific one, and returns that HandlerMethod.

solid answer

~40 s

RequestMappingHandlerMapping extends AbstractHandlerMethodMapping. On startup (afterPropertiesSet) it inspects every @Controller (and @RestController) bean, and for each @RequestMapping-annotated method it builds a RequestMappingInfo — the compound matching criteria: URL patterns, HTTP methods, params, headers, consumable/producible media types. Each RequestMappingInfo is registered against its HandlerMethod in the MappingRegistry. At request time, getHandlerInternal collects all RequestMappingInfos whose conditions match the request, sorts the matches using RequestMappingInfo's comparator (path specificity, then method/params/etc.), and picks the best one. If the top two are equally specific it throws IllegalStateException (ambiguous mapping). If URLs match but the HTTP method/media type doesn't, it raises a targeted 405/415 rather than a plain 404.

code

java · 19 lines
java
@RestController
@RequestMapping("/users")
class UserController {

    // Builds a RequestMappingInfo:
    //   patterns = [/users/{id}]
    //   methods  = [GET]
    //   produces = [application/json]
    @GetMapping(value = "/{id}", produces = "application/json")
    UserDto get(@PathVariable Long id) { ... }

    // Same path, different method + consumes → a DISTINCT RequestMappingInfo.
    // No ambiguity because RequestMethodsRequestCondition differs.
    @PostMapping(consumes = "application/json")
    UserDto create(@RequestBody CreateUser body) { ... }
}

// Two @GetMapping("/{id}") with identical conditions would fail at STARTUP:
//   IllegalStateException: Ambiguous mapping. Cannot map ... method

go deeper

for a junior

Know that @RequestMapping methods are indexed at startup and matched per request by URL + method.

for a middle

Name RequestMappingInfo's condition components and explain the match-then-sort selection and 405/415 signals.

for a senior

Discuss the MappingRegistry structure, getMatchingCondition narrowing, and the comparator's specificity ordering.

for a principal

Reason about extending it (custom conditions, versioning), PathPattern vs AntPathMatcher trade-offs, and ambiguity failure modes.

## What RequestMappingHandlerMapping is `RequestMappingHandlerMapping` (package `org.springframework.web.servlet.mvc.method.annotation`) is the default `HandlerMapping` for annotation controllers. It maps `@RequestMapping` (and its shortcuts `@GetMapping`, `@PostMapping`, etc.) methods to requests. It extends `AbstractHandlerMethodMapping<RequestMappingInfo>`, so its 'mapping key' type is `RequestMappingInfo`. ## Startup: building the registry Because it implements `InitializingBean`, `afterPropertiesSet()` triggers `initHandlerMethods()`: 1. It enumerates candidate beans in the ApplicationContext. 2. `isHandler(beanType)` returns true for classes annotated with `@Controller` or `@RequestMapping`. 3. For each such bean, `detectHandlerMethods` uses reflection to find every method carrying `@RequestMapping` metadata. 4. `getMappingForMethod` builds a **`RequestMappingInfo`** for each method — a composite of independent *condition* objects: - `PatternsRequestCondition` / `PathPatternsRequestCondition` — URL patterns (`/users/{id}`) - `RequestMethodsRequestCondition` — GET/POST/… - `ParamsRequestCondition` — `params = "x=1"` - `HeadersRequestCondition` — `headers = "..."` - `ConsumesRequestCondition` — `consumes = "application/json"` - `ProducesRequestCondition` — `produces = "application/json"` 5. Each `(RequestMappingInfo → HandlerMethod)` pair is stored in the internal **`MappingRegistry`**, which also indexes direct (non-pattern) URL paths for fast lookup. ## Per-request: matching `getHandlerInternal(request)` → `lookupHandlerMethod(lookupPath, request)`: 1. Fast path: check the URL index for `RequestMappingInfo`s with a directly matching path. 2. For each candidate, call `info.getMatchingCondition(request)` — this returns a *new*, narrowed `RequestMappingInfo` if every condition matches, else `null`. 3. Collect all matches into a `List<Match>`. 4. Sort them with `RequestMappingInfo.compareTo` (via `getMatchingComparator`) — more specific patterns, more specific methods, more specific media types win. 5. Take the best match's `HandlerMethod`. ## Edge cases and the specific error signals - **Ambiguous match**: if the two best matches compare as *equal*, Spring throws `IllegalStateException: Ambiguous handler methods mapped…`. - **Path matches but method doesn't**: rather than a bare 404, `lookupHandlerMethod` records the partial matches and throws `HttpRequestMethodNotSupportedException` → HTTP **405**. - **Path + method match but Content-Type wrong**: `HttpMediaTypeNotSupportedException` → **415**. - **Path + method match but Accept unsatisfiable**: `HttpMediaTypeNotAcceptableException` → **406**. - These 'smart' status codes are a key benefit over dumb URL mappings — the registry knows *why* it failed. ## PathPattern vs AntPathMatcher Modern Spring (5.3+) defaults to `PathPatternParser`-based `PathPattern` matching, which is faster and stricter than the legacy `AntPathMatcher`. Which one is used affects trailing-slash and pattern semantics. ## When to care You rely on this every time you write a controller. You *directly* touch it when you subclass `RequestMappingHandlerMapping` to customize (e.g., add API-version conditions via `getCustomMethodCondition`) or debug an 'Ambiguous mapping' startup failure.

  • A request hits an existing URL but with the wrong HTTP method. Why is the response 405 and not 404?
    During lookup, RequestMappingHandlerMapping finds RequestMappingInfos whose path matches but whose RequestMethodsRequestCondition doesn't. It records those partial matches and throws HttpRequestMethodNotSupportedException, which resolves to 405 Method Not Allowed — the registry knows the URL exists.
  • When does the 'Ambiguous mapping' error surface — startup or request time?
    It can surface at request time when two matched RequestMappingInfos compare as equally specific (IllegalStateException). Truly identical mappings on distinct methods are detected at startup during registry building. So potentially both, depending on how the overlap arises.

saying these in an interview costs you the question

  • Claiming the registry is rebuilt on every request (it's built once at startup)
  • Saying wrong-method requests always return 404 (they return 405)
  • Thinking RequestMappingInfo is only the URL path — it includes method, params, headers, consumes, produces
  • Assuming all overlaps are caught at startup

context