skip to content

Multiple HandlerMapping beans exist in a Spring MVC app. In what order are they consulted, and how do RequestMappingHandlerMapping and BeanNameUrlHandlerMapping fit into that ordering?

level: seniorimportance: should knowfreq 45%

answer

  1. First non-null chain wins
  2. Sorted by Ordered — lowest value first
  3. RequestMappingHandlerMapping order 0
  4. BeanNameUrlHandlerMapping after it (bean named /path)
  5. resource/welcome handlers = LOWEST_PRECEDENCE, last

basics

~20 s

The DispatcherServlet keeps all HandlerMapping beans sorted by their Ordered value (lowest first) and asks each in turn until one returns a non-null chain. RequestMappingHandlerMapping is ordered before BeanNameUrlHandlerMapping, so annotated controllers win over URL-named beans.

solid answer

~40 s

DispatcherServlet collects every HandlerMapping bean and sorts them by the Ordered/@Order contract (lowest order value = highest priority). getHandler tries them one by one and returns the first non-null HandlerExecutionChain — first match wins, so ordering resolves cross-mapping conflicts. With MVC auto-config the well-known order is roughly: RequestMappingHandlerMapping (order 0), then BeanNameUrlHandlerMapping, then RouterFunctionMapping, then SimpleUrlHandlerMapping / resource and welcome-page handlers near the end (Ordered.LOWEST_PRECEDENCE region). So an annotated @GetMapping('/hello') beats a bean literally named '/hello'. If you register a custom HandlerMapping you set its order to slot it correctly; ordering only matters when two mappings could both match the same URL.

code

java · 16 lines
java
// Legacy URL-named bean handled by BeanNameUrlHandlerMapping
@Bean(name = "/legacy")
Controller legacyController() {
    return (req, resp) -> { resp.getWriter().write("legacy"); return null; };
}

// Annotated controller — served by RequestMappingHandlerMapping (order 0)
@RestController
class ModernController {
    @GetMapping("/legacy")
    String modern() { return "modern"; }
}

// Because RequestMappingHandlerMapping (order 0) is consulted BEFORE
// BeanNameUrlHandlerMapping, GET /legacy is served by ModernController.modern().
// The /legacy-named bean is shadowed and never reached.

go deeper

for a junior

Know that annotated controllers normally win and mappings are tried in a fixed order.

for a middle

State that DispatcherServlet sorts by Ordered (lowest first) and returns the first non-null chain.

for a senior

Give the concrete lineup — RequestMappingHandlerMapping before BeanNameUrlHandlerMapping, resources last — and how to slot a custom mapping.

for a principal

Design custom routing that coexists safely, reasoning about order collisions, catch-all hazards, and detectAllHandlerMappings.

## Why order exists A Spring MVC context normally has *several* `HandlerMapping` beans at once. Since `DispatcherServlet.getHandler` returns on the **first non-null** result, the sequence in which mappings are consulted directly decides who wins when more than one could match the same request. ## How the order is established 1. At startup `DispatcherServlet.initHandlerMappings` finds all beans of type `HandlerMapping` (by default `detectAllHandlerMappings = true`). 2. It sorts them with `AnnotationAwareOrderComparator`, honoring the `org.springframework.core.Ordered` interface and `@Order` annotation. 3. **Lower order value = earlier = higher priority.** `Ordered.HIGHEST_PRECEDENCE = Integer.MIN_VALUE`, `LOWEST_PRECEDENCE = Integer.MAX_VALUE`. ## The default lineup (Spring Boot / MVC config) Approximate order values from `WebMvcConfigurationSupport`: - `RequestMappingHandlerMapping` → **order 0** (annotated controllers; highest normal priority). - `ViewControllerRegistry` / view-controller mapping → order 1. - `BeanNameUrlHandlerMapping` → order 2 (beans whose *name* is a URL path like `/legacy`). - `RouterFunctionMapping` → order 3 (functional endpoints) — note in some versions functional mapping is ordered ahead; the exact numbers vary by Spring version, but annotation mapping is first. - `SimpleUrlHandlerMapping` for resources → `Ordered.LOWEST_PRECEDENCE - 1`. - Welcome-page / default handler → `LOWEST_PRECEDENCE`. The stable, interview-relevant fact: **RequestMappingHandlerMapping is consulted before BeanNameUrlHandlerMapping**, so annotation controllers take precedence over URL-named beans, and resource/welcome handlers are last so they only catch what nothing else claimed. ## BeanNameUrlHandlerMapping specifics `BeanNameUrlHandlerMapping` maps a request URL to a bean whose *name starts with '/'*. Define `@Bean(name = "/hello") Controller hello()` and a GET `/hello` can be dispatched to it — a legacy style predating annotation controllers. Because it sits *after* `RequestMappingHandlerMapping`, an `@GetMapping("/hello")` on a real controller shadows the `/hello`-named bean. ## Setting order on a custom mapping ```java @Bean public HandlerMapping myMapping() { MyHandlerMapping m = new MyHandlerMapping(); m.setOrder(-1); // before RequestMappingHandlerMapping (order 0) return m; } ``` `AbstractHandlerMapping` implements `Ordered`; its default order is `LOWEST_PRECEDENCE`, so a custom mapping added without setOrder ends up last. ## Gotchas - Ordering is only decisive when **two mappings both match**. If only one matches, order is irrelevant. - A common trap: adding a broad catch-all custom mapping with a low (high-priority) order that swallows requests meant for annotated controllers. - `detectAllHandlerMappings=false` makes DispatcherServlet look up a *single* bean named `handlerMapping` instead — rarely used, but it disables auto-detection/sorting. - Two `HandlerMapping`s with the *same* order value have undefined relative order among themselves — avoid that. ## When to use each - Everyday REST/MVC: `RequestMappingHandlerMapping` (implicit). - Legacy/DSL-ish URL→bean wiring: `BeanNameUrlHandlerMapping` or `SimpleUrlHandlerMapping`. - Custom routing (multitenancy, API versioning by header, feature flags): a custom mapping slotted via `setOrder`.

  • You add a custom HandlerMapping and it never seems to win. What's the likely cause and fix?
    AbstractHandlerMapping defaults to LOWEST_PRECEDENCE, so your mapping is consulted last and other mappings match first. Call setOrder() with a lower value (e.g. -1) so it's tried before RequestMappingHandlerMapping.
  • Does ordering matter if a request only matches one mapping?
    No. Order only decides conflicts. If exactly one HandlerMapping returns a non-null chain, that one is used regardless of position, and the earlier ones simply returned null.

saying these in an interview costs you the question

  • Saying higher order value = higher priority (it's the opposite — lowest wins)
  • Claiming BeanNameUrlHandlerMapping overrides annotation controllers
  • Thinking DispatcherServlet merges results from multiple mappings instead of taking the first non-null
  • Believing resource handlers are consulted first

context