jongkwan.dev
Development · Essay №066

The Spring MVC Request Flow

The stages an HTTP request passes through, from DispatcherServlet to handler mapping, adapters, and view resolvers, until it becomes a response.

Jongkwan Lee2026년 3월 21일7 min read
Contents

Every Spring MVC request funnels into a single DispatcherServlet and then follows a fixed sequence of stages to become a response.

A single entry point: the front controller

Spring MVC works as a front controller. In that pattern, one object receives every request, finishes the shared handling, and then delegates to the appropriate controller. That entry point object is the DispatcherServlet.

DispatcherServlet is an ordinary servlet extending HttpServlet. Spring Boot maps it automatically to every path (/). When a request arrives, the standard servlet entry point service() is called. Its parent class FrameworkServlet overrides it, and the chain eventually reaches the core method doDispatch().

Because there is only one entry point, cross-cutting concerns such as logging, authentication, and exception translation can be handled in one place. Individual controllers only need to concentrate on their own business logic. Every remaining stage in the flow happens on top of doDispatch().

The eight stages inside doDispatch

Turning one request into a response breaks into three groups of work: find the handler, run it, and convert its result into a response.

StageComponentWork
1HandlerMappingLook up the handler (controller) mapped to the request URL
2HandlerAdapterLook up an adapter capable of running that handler
3HandlerAdapterExecute the adapter that was found
4ControllerThe adapter invokes the actual handler
5HandlerAdapterConvert the return value into a ModelAndView and return it
6ViewResolverRun the view resolver with the view name
7ViewResolverTurn the logical name into a physical name and return a View
8ViewRender the view with the model

This table describes the traditional view-rendering path. For a REST response, a message converter serializes the returned object into a body such as JSON instead of stages 6 through 8. Both paths go through stages 1 through 5 identically.

Handler mapping and handler adapters

Finding a controller and running it are separate jobs because controllers do not all take the same form. Handler mapping answers "who handles this request" and the handler adapter answers "how is that handler executed". That split lets annotation-based controllers and the older interface-based controllers coexist in the same flow.

Spring Boot registers several of each automatically, in priority order. Handler mapping uses the first one that matches, and the handler adapter uses the first adapter whose supports() returns true.

KindBean nameHandles
HandlerMappingRequestMappingHandlerMappingAnnotation controllers based on @RequestMapping
HandlerMappingBeanNameUrlHandlerMappingHandlers looked up by Spring bean name
HandlerAdapterRequestMappingHandlerAdapterAnnotation controllers
HandlerAdapterHttpRequestHandlerAdapterHttpRequestHandler implementations
HandlerAdapterSimpleControllerHandlerAdapterImplementations of the older Controller interface

Nearly all production controllers are built on @Controller and @RequestMapping, so the RequestMappingHandlerMapping and RequestMappingHandlerAdapter pair is what usually runs. The remaining adapters are compatibility paths for static resources and legacy-style controllers.

Argument binding and response conversion

Two collaborators step in when the handler adapter executes a controller method. An ArgumentResolver fills the parameters and a ReturnValueHandler turns the return value into a response. Both call an HttpMessageConverter when needed to convert a body into an object and an object into a body.

Take request body handling as an example. @RequestBody is handled by RequestResponseBodyMethodProcessor, and HttpEntity by HttpEntityMethodProcessor; both are ArgumentResolvers. They call a message converter to turn a JSON body into a Java object. On the response side, the ReturnValueHandler for @ResponseBody serializes the object with the same converter.

java
@RestController
public class MemberController {
 
    @PostMapping("/members")
    public MemberResponse create(@RequestBody MemberRequest request) {
        // request: the message converter turned the JSON body into an object
        // return value: the ReturnValueHandler serializes it back into a JSON body
        return new MemberResponse(request.getName());
    }
}

If the return value is a view name rather than an object, the flow takes the view resolver path at stage 6. The view resolver takes a logical name such as new-form and turns it into an actual file path. The InternalResourceViewResolver that Spring Boot registers automatically does this work. It completes the physical path by wrapping the name in the spring.mvc.view.prefix and spring.mvc.view.suffix settings.

yaml
spring:
  mvc:
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp

With that configuration, a controller returning new-form resolves to /WEB-INF/views/new-form.jsp. The controller only has to know the logical name, and the actual path convention is left to configuration. The controller code stays untouched even when the view technology changes.

Where filters and interceptors sit

There are two places to insert cross-cutting concerns. A filter sits in the servlet layer and wraps the outside of DispatcherServlet, while an interceptor sits inside Spring MVC and wraps the controller on both sides. Even when they do the same job, the information they can reach and their position differ.

A filter is standard servlet technology, so it only sees request and response. The Filter interface has a simple set of methods. Inside doFilter you must call chain.doFilter() to pass control to the next filter or the servlet; skip the call and the request ends there.

java
public interface Filter {
    default void init(FilterConfig filterConfig) throws ServletException {}
 
    void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;
 
    default void destroy() {}
}

An interceptor is provided by Spring MVC, so it also receives which handler is being invoked and which ModelAndView is returned. HandlerInterceptor splits into three points in time. preHandle runs before the handler adapter is called, postHandle after, and afterCompletion once view rendering has finished.

java
public interface HandlerInterceptor {
    default boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {
        return true;
    }
 
    default void postHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {}
 
    default void afterCompletion(HttpServletRequest request,
            HttpServletResponse response, Object handler,
            Exception ex) throws Exception {}
}

The invocation rules for those three methods are the heart of interceptor design. If preHandle returns false, neither the remaining interceptors nor the handler adapter is invoked and the flow ends there. That fits logic that has to block a request, such as a login check.

Behavior diverges when an exception occurs. If a controller throws, postHandle is skipped, but afterCompletion always runs and receives the exception object ex as a parameter. Shared cleanup that must happen regardless of exceptions therefore belongs in afterCompletion.

AspectFilterInterceptor
Provided byServlet containerSpring MVC
PositionOutside DispatcherServletAround the controller
Accessible informationrequest, responseIncludes handler and ModelAndView
How to blockDo not call chain.doFilterReturn false from preHandle

Summed up in one line, concerns outside MVC such as encoding or a security filter chain belong to filters, and processing that needs controller context belongs to interceptors. Spring Security being filter-based follows from the same reasoning.

Summary

The Spring MVC request flow starts at a single entry point, the DispatcherServlet. doDispatch() finds a controller through handler mapping, runs it through a handler adapter, and then builds the response through either a view resolver or a message converter. Argument binding and return value conversion belong to ArgumentResolver and ReturnValueHandler, while body serialization belongs to the message converter.

Cross-cutting concerns split between filters in the servlet layer and interceptors inside MVC. The three interceptor callbacks define where blocking and exception handling happen. Keeping this staging in mind makes it much easier to trace where validation, exception translation, and authentication each take place.