Spring MVC Request Lifecycle (DispatcherServlet Flow)
Fast recall
Quick revision
Core answer
Understanding of Spring MVC internals and ability to reason about what happens at each stage when debugging production issues.
Candidates often jump straight to the controller and skip DispatcherServlet, HandlerMapping, and HandlerInterceptors, missing three of the most important extension points.
Mention that DispatcherServlet is itself a Servlet registered via web.xml or ServletContainerInitializer, and explain how HandlerInterceptors differ from Filters in terms of Spring context awareness.
Interview-ready guide
Interview answer
Every HTTP request to a Spring MVC application passes through a fixed pipeline. Knowing each stage is what allows you to place cross-cutting logic in the right layer.
The pipeline
- The request hits the servlet container (Tomcat), which forwards it to
DispatcherServlet— the front controller registered for/*or/ HandlerMappingresolves which controller method handles this URL + HTTP method combinationHandlerInterceptor.preHandle()runs — authentication checks, logging, rate limiting at the MVC layerHandlerAdapterinvokes the controller method — resolving@RequestParam,@PathVariable,@RequestBodyfrom the requestHttpMessageConverterreads the request body (JSON → Java object) and writes the response body (Java object → JSON)HandlerInterceptor.postHandle()runs after the handler but before the view is rendered- The response is written;
HandlerInterceptor.afterCompletion()runs regardless of success or exception
Where exceptions go
- Exceptions thrown by the handler method are caught by
HandlerExceptionResolver @ExceptionHandlermethods in a@ControllerAdviceare picked up byExceptionHandlerExceptionResolver
The distinction between Filter and HandlerInterceptor: a Filter executes before DispatcherServlet sees the request — it has no knowledge of which controller will handle it. An interceptor executes inside DispatcherServlet, after the handler is resolved.
Spring MVC Request Lifecycle (DispatcherServlet Flow)
Direct Answer
Spring MVC uses a single front controller, DispatcherServlet, which delegates to a chain of specialized components: HandlerMapping finds the controller method, HandlerAdapter invokes it, ViewResolver (or message converters for REST) renders the response, and exception resolvers handle errors along the way.
Code Example
// 1) Servlet Filter — runs OUTSIDE Spring MVC; first to see the request,
// last to see the response. Ideal for auth, CORS, request/response buffering.
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
class RequestIdFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
String id = Optional.ofNullable(req.getHeader("X-Request-Id"))
.orElseGet(() -> UUID.randomUUID().toString());
MDC.put("requestId", id); // visible to every log line below
res.setHeader("X-Request-Id", id);
try {
chain.doFilter(req, res); // down to DispatcherServlet
} finally {
MDC.clear();
}
}
}
// 2) HandlerInterceptor — runs INSIDE Spring MVC after HandlerMapping has
// picked the controller method. Ideal for per-endpoint concerns that
// need the HandlerMethod (annotations, controller class).
@Component
class AuditInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
if (handler instanceof HandlerMethod hm) {
Audit ann = hm.getMethodAnnotation(Audit.class);
if (ann != null) req.setAttribute("auditAction", ann.value());
}
return true; // proceed to controller
}
@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse res,
Object handler, Exception ex) {
Object action = req.getAttribute("auditAction");
if (action != null) auditLog.record(action, req, res, ex);
}
}
@Configuration
class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry reg) {
reg.addInterceptor(new AuditInterceptor())
.addPathPatterns("/v1/**")
.excludePathPatterns("/v1/health");
}
}
// 3) @ControllerAdvice — runs AFTER the controller; central place for
// exception translation and response body post-processing.
@RestControllerAdvice
class GlobalErrors extends ResponseEntityExceptionHandler {
@ExceptionHandler(NotFoundException.class)
ProblemDetail notFound(NotFoundException ex) {
ProblemDetail pd = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
pd.setProperty("code", ex.code());
pd.setProperty("traceId", MDC.get("requestId"));
return pd;
}
}
// Execution order for a request that throws:
// RequestIdFilter.doFilterInternal (pre)
// -> DispatcherServlet
// -> AuditInterceptor.preHandle
// -> HandlerAdapter -> OrderController.get() --throws NotFoundException-->
// -> AuditInterceptor.afterCompletion (with ex)
// -> @ControllerAdvice.notFound builds ProblemDetail
// -> Filters see the (translated) response
// RequestIdFilter cleans MDC
Common Mistakes & Pitfalls
Candidates often jump straight to the controller and skip DispatcherServlet, HandlerMapping, and HandlerInterceptors, missing three of the most important extension points.
Check your understanding
Answer these without looking back at the article.
- How would you add a custom HandlerInterceptor to measure request latency for every endpoint?
- What is the difference between a HandlerInterceptor and a Servlet Filter in terms of what they can access?
- How does DispatcherServlet know which HttpMessageConverter to use when writing a response body?
Related interview prep categories
1Want a guided curriculum order?
These modules are also sequenced in our flagship backend interview track — open it when you prefer an ordered path instead of browsing by topic.