- Chapter
- Chapter 1 — Spring MVC Architecture
- Prerequisites
- Topic 1.1; Volume III Chapter 7 (embedded servers)
- You will be able to
- Explain the DispatcherServlet's role and strategy beans, how it routes requests and selects handlers, and how it processes responses and errors
- Covers
- Front Controller · Request Routing · Handler Selection · Response Processing
On this page
- 1. Concept / Definition
- 2. Why It Exists
- 3. Core Concepts
- 4. How It Works Internally
- 4A. Deep Dive — Customising the Front Controller the Right Way
- 5. APIs / Features / Syntax
- 6. Practical Examples
- 7. Common Problems & Mistakes
- 8. Performance & Production Considerations
- 9. Architecture & Design Considerations
- 10. Interview Questions & Answers
1Concept / Definition
The DispatcherServlet is Spring MVC's front controller: a single jakarta.servlet.Servlet that receives every request for the application (mapped to /), finds the right handler, invokes it through an adapter, and produces the response — delegating each step to pluggable strategy beans from the Spring application context.

| Strategy bean | Role | Boot default (servlet stack) |
|---|---|---|
HandlerMapping | Find handler + interceptors for a request | RequestMappingHandlerMapping, RouterFunctionMapping, welcome-page and resource mappings |
HandlerAdapter | Invoke a handler of a given type | RequestMappingHandlerAdapter, HandlerFunctionAdapter, … |
HandlerExceptionResolver | Turn exceptions into responses | Exception handler, response status and default resolvers |
ViewResolver | Map view names to views | ContentNegotiatingViewResolver, template resolvers when present |
LocaleResolver, FlashMapManager, RequestToViewNameTranslator | Locale, flash attributes, default view names | Accept-header locale, session flash map |
2Why It Exists
Plain servlet applications mapped one servlet per URL pattern and repeated cross-cutting logic in each. The front controller pattern centralises request handling: one component applies common processing (locale, multipart, exception handling, view rendering) and dispatches to fine-grained handlers. Spring's design goes further by making every step a strategy interface, so the same DispatcherServlet supports annotated controllers, functional endpoints, static resources and legacy handler types.
3Core Concepts
3.1 Front Controller

In Spring Boot, DispatcherServletAutoConfiguration creates the servlet as a bean and registers it with the embedded server at path / (configurable with spring.mvc.servlet.path). It shares the application context with the rest of the application (Boot uses a single context rather than the classic root/servlet hierarchy).
3.2 Request Routing
Routing is layered:
| Layer | Decides |
|---|---|
| Servlet container | Which servlet: filters + DispatcherServlet mapped to / |
| Servlet filters | Whether the request proceeds (security, CORS, encoding) |
| DispatcherServlet | Which handler, via ordered HandlerMappings |
RequestMappingInfo conditions | Which controller method (path, method, params, headers, media types, version) |
DEBUG o.s.web.servlet.DispatcherServlet : GET "/api/orders/o-17", parameters={}
DEBUG s.w.s.m.m.a.RequestMappingHandlerMapping :
Mapped to com.acme.OrderApiController#find(String)
DEBUG o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*]
DEBUG o.s.web.servlet.DispatcherServlet : Completed 200 OK3.3 Handler Selection

getHandler(request) asks each HandlerMapping in order (annotated controllers first, static resources last); the first non-null HandlerExecutionChain wins. The chain contains the handler (e.g. HandlerMethod) and the HandlerInterceptors that apply to it. If no mapping matches, Spring MVC produces a 404 (NoHandlerFoundException, handled by default in Boot as a Problem Detail or error page).
getHandlerAdapter(handler) then asks each HandlerAdapter whether it supports(handler) — decoupling the dispatcher from handler types.
3.4 Response Processing

| Handler result | Processing |
|---|---|
ModelAndView with view name | ViewResolver → View.render() |
Body already written (@ResponseBody, ResponseEntity) | Return value handler used a message converter; no view |
Async result (CompletableFuture, DeferredResult) | Request put into async mode; dispatched again when complete |
| Exception | HandlerExceptionResolvers → error response or error view |
4How It Works Internally
1. FrameworkServlet.service() → processRequest() sets up LocaleContextHolder and RequestContextHolder, then calls DispatcherServlet.doService().
2. doService() exposes framework attributes (context, locale resolver, flash map) on the request and calls doDispatch().
3. doDispatch(): check multipart → getHandler → getHandlerAdapter → HTTP caching check (Last-Modified) → interceptors' preHandle → ha.handle() → default view name → postHandle → processDispatchResult (render or resolve exception) → afterCompletion.
4. Strategies are initialised at start-up (initStrategies) from beans in the context, falling back to defaults in DispatcherServlet.properties when none are defined (Boot defines them).
5. After processing, request-scoped resources are cleaned up and a ServletRequestHandledEvent may be published.
4ADeep Dive — Customising the Front Controller the Right Way
| Need | Right extension point |
|---|---|
| Add a custom mapping style | Register a HandlerMapping bean (with order) |
| Support a new handler type | Register a HandlerAdapter bean |
| Cross-cutting pre/post logic for handlers | HandlerInterceptor via WebMvcConfigurer.addInterceptors |
| Request-level logic before Spring MVC | Servlet Filter / OncePerRequestFilter |
| Global exception mapping | @ControllerAdvice or HandlerExceptionResolver bean |
| Change servlet path, load-on-startup | spring.mvc.servlet.path, spring.mvc.servlet.load-on-startup |
| Tweak MVC defaults without losing Boot | Implement WebMvcConfigurer (without @EnableWebMvc) |
@Configuration
class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TenantInterceptor()).addPathPatterns("/api/**");
}
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("/api",
HandlerTypePredicate.forAnnotation(RestController.class));
}
}5APIs / Features / Syntax
| API / property | Purpose |
|---|---|
DispatcherServlet, FrameworkServlet | Front controller classes |
HandlerMapping, HandlerExecutionChain | Handler lookup |
HandlerAdapter | Handler invocation |
HandlerInterceptor | Pre/post handler hooks |
HandlerExceptionResolver | Exception to response |
ViewResolver, View | Rendering |
WebMvcConfigurer | Customisation callbacks |
spring.mvc.servlet.path, spring.mvc.servlet.load-on-startup | Servlet registration |
logging.level.org.springframework.web=DEBUG | Trace dispatching |
6Practical Examples
6.1 Handler interceptor for timing
class TimingInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res,
Object handler) {
req.setAttribute("start", System.nanoTime());
return true; // false = stop processing
}
@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse res,
Object handler, Exception ex) {
long ms = (System.nanoTime() - (long) req.getAttribute("start")) / 1_000_000;
log.info("{} {} → {} in {} ms", req.getMethod(), req.getRequestURI(),
res.getStatus(), ms);
}
}6.2 Functional endpoint alongside annotated controllers
@Bean
RouterFunction<ServerResponse> healthRoutes() {
return RouterFunctions.route()
.GET("/ping", req -> ServerResponse.ok().body("pong"))
.build(); // served via RouterFunctionMapping
}6.3 Load on start-up
spring:
mvc:
servlet:
# initialise DispatcherServlet at start-up, not on first request
load-on-startup: 16.4 Inspecting mappings at runtime
curl -s localhost:9090/actuator/mappings | jq '.contexts[].mappings.dispatcherServlets
.dispatcherServlet | length'6.5 Typical handler mapping order in a Boot MVC app
| Precedence | Mapping | Handles |
|---|---|---|
| Highest (order 0) | RequestMappingHandlerMapping | @RequestMapping methods |
| Middle | View controllers, BeanNameUrlHandlerMapping, RouterFunctionMapping, welcome page | Simple view mappings, bean-named handlers, functional endpoints, / → index.html |
| Lowest | SimpleUrlHandlerMapping for resources | /** static resources from classpath:/static/ etc. |
Because resource handling has the lowest precedence, an @GetMapping("/app.js") wins over static/app.js, and unmatched paths fall through to static resources before resulting in 404.
6.6 Error dispatch
Controller throws → exception resolvers produce no response
→ container forwards to /error (ERROR dispatch)
→ DispatcherServlet handles /error → BasicErrorController (JSON or HTML)The DispatcherServlet processes the error dispatch like a normal request, which is why filters registered only for REQUEST dispatch do not run for errors.
7Common Problems & Mistakes
- @EnableWebMvc in Boot apps — defaults disappear.
- Filters vs interceptors confusion — security belongs in filters (before MVC), handler-specific logic in interceptors.
- First-request latency because the DispatcherServlet initialises lazily — set
load-on-startup. - Ambiguous mappings discovered only at start-up — keep URL spaces cohesive per controller.
- Custom HandlerMapping without order — unexpected precedence.
These mistakes have recognisable symptoms. Ambiguous mappings fail start-up with an IllegalStateException naming both controller methods, which is good, but overlapping path patterns such as /orders/{id} and /orders/export are resolved silently by specificity, so check the Actuator mappings endpoint when a request reaches the wrong method. Interceptors have their own contract: if preHandle returns false, the interceptor must write the response itself, otherwise the client receives an empty 200; afterCompletion runs only for interceptors whose preHandle returned true; and for async handlers postHandle runs only after the async result completes, so use AsyncHandlerInterceptor for per-request clean-up. Interceptors also apply to static resource handlers unless you add excludePathPatterns, and they never run for requests rejected earlier by Spring Security's filters, so a 401 never appears in interceptor-based metrics.
8Performance & Production Considerations
| Concern | Guidance |
|---|---|
| Dispatch overhead | Microseconds; mapping lookup is indexed and cached |
| Interceptors | Run on every matched request — keep them cheap |
| Logging | DEBUG on org.springframework.web is verbose; use only for diagnosis |
| Observability | http.server.requests timers cover the whole dispatch |
9Architecture & Design Considerations
- Extend through strategies and configurers, keeping Boot's defaults.
- Separate filter-level concerns (security, CORS, request ids) from handler-level ones.
- Consistent URL prefixes (
/api) configured centrally. - Functional endpoints for lightweight or programmatically composed routes; annotated controllers for most APIs.
10Interview Questions & Answers
What is the DispatcherServlet?
The DispatcherServlet is Spring MVC's front controller: a single jakarta.servlet.Servlet, normally mapped to /, that receives every request for the application and coordinates its handling. It does not contain business logic or routing decisions itself. For each request it asks HandlerMapping beans which handler applies, uses a HandlerAdapter to invoke it, lets HandlerExceptionResolvers turn exceptions into responses, and uses ViewResolvers when a view must be rendered. All these strategies are beans from the Spring context, which is why annotated controllers, functional endpoints and static resources can coexist behind one servlet. In Spring Boot it is created by DispatcherServletAutoConfiguration and registered with the embedded server; you rarely declare it yourself.
Which strategy finds the controller method for a request?
A HandlerMapping finds the handler, and for annotated controllers it is RequestMappingHandlerMapping. At start-up it scans beans annotated with @Controller, reads each @RequestMapping, @GetMapping or similar annotation and registers a RequestMappingInfo combining the path pattern, HTTP method, parameters, headers, consumes and produces media types and, in Framework 7, the API version. At request time it collects every mapping whose conditions match, chooses the most specific one and returns a HandlerExecutionChain holding the HandlerMethod plus the applicable interceptors. If two mappings are equally specific, start-up fails with an ambiguous-mapping error. The DispatcherServlet asks all HandlerMappings in order, so functional routes and static resources are found by other mappings.
Why does the DispatcherServlet use HandlerAdapters?
Because the DispatcherServlet must invoke handlers of very different types without knowing any of them. A handler might be a HandlerMethod on an annotated controller, a HandlerFunction from a RouterFunction, a legacy Controller implementation or an HttpRequestHandler serving resources. For each handler, the dispatcher asks its HandlerAdapters which one supports it, then calls adapter.handle(). RequestMappingHandlerAdapter does the heavy lifting for annotated controllers: resolving arguments such as @PathVariable and @RequestBody, data binding and validation, invoking the method and processing the return value through message converters. This is the adapter pattern applied to dispatching, and it is why a new programming model like functional endpoints could be added without changing the DispatcherServlet itself.
What is the difference between a servlet filter and a handler interceptor?
A servlet filter is part of the servlet container's chain and runs before the DispatcherServlet, for every request matching its URL pattern, including static resources and requests that will never reach a controller. It works only with the raw request and response, can wrap them, and can stop the chain entirely. Spring Security, CORS, character encoding and request-id propagation belong there. A HandlerInterceptor runs inside Spring MVC, after the handler has been selected, so it knows which controller method will be invoked, can inspect its annotations, and has preHandle, postHandle and afterCompletion callbacks. Choose a filter for protocol-level or security concerns, and an interceptor for logic that depends on the handler.
Describe doDispatch().
doDispatch() is the core algorithm. It first checks for a multipart request and wraps it if needed. It then calls getHandler(), iterating the HandlerMappings until one returns a HandlerExecutionChain; if none does, it produces a 404. Next it finds a HandlerAdapter that supports the handler, performs the Last-Modified check for GET and HEAD, and runs each interceptor's preHandle, stopping if any returns false. It invokes ha.handle(), returns early if async processing started, applies a default view name and runs postHandle. processDispatchResult() then either resolves an exception through the HandlerExceptionResolvers or renders the view. Finally afterCompletion runs for the interceptors, and multipart resources are cleaned up.
What happens if you add @EnableWebMvc to a Spring Boot application?
@EnableWebMvc imports DelegatingWebMvcConfiguration, which registers a WebMvcConfigurationSupport bean, and Boot's WebMvcAutoConfiguration is conditional on that bean being missing, so it backs off completely. The application still works, but Boot's customisations disappear: message converters configured from Boot's ObjectMapper and properties, static resource handling from classpath:/static, the welcome page, spring.mvc properties, format settings and parts of the error-handling integration. Symptoms appear as changed JSON date formats, missing static files or ignored properties after someone copies an @EnableWebMvc class from a non-Boot tutorial. To customise MVC in Boot, implement WebMvcConfigurer in a normal @Configuration class without @EnableWebMvc; use the annotation only when you deliberately want to own all MVC configuration.
How would you add a tenant check to all API requests?
It depends on what the check needs. If the tenant comes from the authenticated principal, such as a JWT claim, and requests without a valid tenant must be rejected before any application code runs, implement it in the security layer: an AuthorizationManager rule or a filter added to the API SecurityFilterChain after authentication. That covers every path, including static and error dispatches. If the check depends on the handler, for example only controllers annotated with @TenantScoped, use a HandlerInterceptor registered for /api paths through WebMvcConfigurer, which can read the handler method's annotations. Either way, keep the resolved tenant in a request-scoped holder, clear it after the request, and return a consistent Problem Detail on failure.
Why is the strategy-based design of the DispatcherServlet important for long-lived platforms?
Because it lets the framework, and the platforms built on it, evolve without breaking applications. The DispatcherServlet holds a stable algorithm, while every decision is delegated to strategy interfaces: HandlerMapping, HandlerAdapter, HandlerExceptionResolver, ViewResolver and others. Spring has used this to add annotated controllers, async request processing, functional endpoints and, in Framework 7, API versioning, all as new strategies, while code written against older models kept working. Platform teams can do the same: contribute interceptors, exception resolvers, argument resolvers or configurers through internal starters, rather than subclassing or wrapping the servlet. Extensions written that way survive framework upgrades, can be overridden per service, and keep applications on the documented, supported extension surface.