Spring BootDispatcherServlet
Spring Boot

DispatcherServlet

DispatcherServlet is the front controller of Spring MVC. Every HTTP request handled by a Spring Boot web application passes through it. It delegates to HandlerMappings, HandlerAdapters, ViewResolvers, and ExceptionResolvers — coordinating the full request lifecycle without containing any routing or business logic itself. Spring Boot auto-configures and registers DispatcherServlet automatically; understanding how it works is essential for advanced customisation, debugging, and multi-context setups.

Front Controller Pattern

The Front Controller pattern centralises all request handling through a single entry point. Instead of each resource type having its own servlet, one controller receives every request and delegates to specialised handlers. This gives a single place to enforce cross-cutting concerns — authentication, logging, content negotiation, error handling — before any business logic runs. DispatcherServlet is Spring MVC's implementation of this pattern. It is a standard Java EE servlet registered with the embedded Tomcat (or Jetty/Undertow) container. Its sole responsibility is coordination: find the right handler, invoke it through the right adapter, convert the result to a response, and handle any exceptions uniformly. It contains no routing logic of its own — all routing knowledge lives in the HandlerMappings it delegates to.

Auto-Configuration in Spring Boot

Spring Boot's DispatcherServletAutoConfiguration registers and configures DispatcherServlet automatically. You do not need to declare it in web.xml or register it programmatically. The servlet is mapped to / by default — it handles all requests.
yaml
# ── What Spring Boot auto-configures ─────────────────────────────────
# 1. Creates a DispatcherServlet bean
# 2. Registers it with the embedded Tomcat container mapped to "/"
# 3. Creates the ApplicationContext (WebApplicationContext) it uses
# 4. Auto-detects HandlerMappings, HandlerAdapters, ViewResolvers,
#    MessageConverters, ExceptionResolvers in the context

# ── Key application.yml properties ────────────────────────────────────
spring:
  mvc:
    servlet:
      path: /          # servlet mapping (default: /) — change to /api/* to expose
                       # other servlets at the root
    static-path-pattern: /static/**    # static resource path pattern
    throw-exception-if-no-handler-found: true  # 404 as exception, not silent
  web:
    resources:
      add-mappings: false  # disable static resource handler (pure API apps)

server:
  servlet:
    context-path: /myapp   # all URLs prefixed with /myapp

# ── Checking DispatcherServlet registration ───────────────────────────
# At DEBUG log level, Spring Boot logs:
# "Mapped to com.example.UserController#findById(Long)"
# "Completed 200 OK"
logging:
  level:
    org.springframework.web.servlet.DispatcherServlet: DEBUG

DispatcherServlet Initialisation

On startup, DispatcherServlet calls initStrategies() to discover and initialise each component type from the ApplicationContext. If a component is not found in the context, a default implementation from DispatcherServlet.properties is used. This is how Spring MVC works out of the box without any explicit configuration.
Java
// ── Components DispatcherServlet initialises (from DispatcherServlet.properties):
// initMultipartResolver()       — MultipartResolver (file uploads)
// initLocaleResolver()          — LocaleResolver (i18n)
// initThemeResolver()           — ThemeResolver (UI themes — deprecated)
// initHandlerMappings()         — List<HandlerMapping>
// initHandlerAdapters()         — List<HandlerAdapter>
// initHandlerExceptionResolvers() — List<HandlerExceptionResolver>
// initRequestToViewNameTranslator() — RequestToViewNameTranslator
// initViewResolvers()           — List<ViewResolver>
// initFlashMapManager()         — FlashMapManager (redirect attributes)

// ── Default strategies (used when no bean found in context): ──────────
// HandlerMapping:  BeanNameUrlHandlerMapping,
//                  RequestMappingHandlerMapping (Spring Boot adds this),
//                  RouterFunctionMapping
// HandlerAdapter:  HttpRequestHandlerAdapter,
//                  SimpleControllerHandlerAdapter,
//                  RequestMappingHandlerAdapter (Spring Boot adds this)
// ExceptionResolver: ExceptionHandlerExceptionResolver,
//                    ResponseStatusExceptionResolver,
//                    DefaultHandlerExceptionResolver
// ViewResolver:    InternalResourceViewResolver (for JSP — not used in REST APIs)

// ── Observing initialisation at DEBUG ─────────────────────────────────
logging:
  level:
    org.springframework.web.servlet: DEBUG
// Logs lines like:
// "Detected 2 HandlerMappings in servlet 'dispatcherServlet'"
// "Detected 3 HandlerAdapters in servlet 'dispatcherServlet'"
// "Detected 3 HandlerExceptionResolvers in servlet 'dispatcherServlet'"

Request Processing — doDispatch()

Every request is handled by DispatcherServlet.doDispatch(). Understanding this method's sequence explains why exceptions appear where they do, why interceptors run in a specific order, and what happens when no handler is found.
Java
// Simplified pseudocode of DispatcherServlet.doDispatch():

protected void doDispatch(HttpServletRequest request,
                          HttpServletResponse response) throws Exception {

    // 1. Check for multipart (file upload) — wrap if needed:
    HttpServletRequest processedRequest = checkMultipart(request);

    // 2. Find the handler (controller method) for this request:
    HandlerExecutionChain mappedHandler = getHandler(processedRequest);
    // Iterates HandlerMappings in priority order.
    // If none match → noHandlerFound() → 404 or NoHandlerFoundException

    // 3. Find the HandlerAdapter that can invoke this handler type:
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

    // 4. Run preHandle() on all interceptors in the chain:
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        return;    // an interceptor returned false — stop processing
    }

    // 5. Invoke the handler (your @RestController method):
    ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    // ArgumentResolvers bind parameters
    // Handler method executes
    // ReturnValueHandlers process the return value
    // For @ResponseBody: HttpMessageConverter writes body to response

    // 6. Run postHandle() on interceptors (reverse order):
    mappedHandler.applyPostHandle(processedRequest, response, mv);

    // 7. Process the result — resolve view or handle exceptions:
    processDispatchResult(processedRequest, response, mappedHandler, mv, exception);
    // For REST: no view to resolve — body was already written by MessageConverter
    // For exceptions: delegates to HandlerExceptionResolvers

    // 8. Run afterCompletion() on interceptors (always — even on exception):
    mappedHandler.triggerAfterCompletion(processedRequest, response, exception);
}

ApplicationContext Hierarchy

Spring Boot creates a single ApplicationContext for the entire application. Classic Spring MVC applications used two contexts — a root context and a servlet context. Understanding both patterns helps when migrating legacy apps or diagnosing bean visibility issues.
Java
// ── Spring Boot (single context — the modern approach): ──────────────
//
//   ApplicationContext (one context)
//   ├── @Service, @Repository, @Component beans
//   ├── @RestController, @Controller beans
//   ├── HandlerMappings, HandlerAdapters, MessageConverters
//   └── Security, DataSource, JPA, etc.
//
// DispatcherServlet uses this single context.
// All beans are visible to each other — no parent/child visibility rules.

// ── Classic Spring MVC (two contexts — legacy): ───────────────────────
//
//   Root ApplicationContext (loaded by ContextLoaderListener)
//   ├── @Service, @Repository, @Component beans
//   └── DataSource, JPA, etc.
//       ▲ parent — child can see parent beans, not vice versa
//   Servlet ApplicationContext (one per DispatcherServlet)
//   ├── @Controller, @RestController beans
//   ├── HandlerMappings, HandlerAdapters, ViewResolvers
//   └── Can see root context beans via parent reference
//
// Common mistake in legacy apps: defining @Service in the servlet context
// instead of the root context → invisible to other servlets / schedulers.

// ── Accessing the WebApplicationContext: ─────────────────────────────
@RestController
@RequiredArgsConstructor
public class ContextDebugController {

    private final ApplicationContext context;   // injected normally

    @GetMapping("/debug/beans")
    public List<String> listBeans() {
        return Arrays.asList(context.getBeanDefinitionNames());
    }
}

Customising DispatcherServlet

Spring Boot exposes DispatcherServlet configuration through application.yml properties and DispatcherServletRegistrationBean. Direct subclassing is rarely needed but is supported.
yaml
// ── application.yml — common DispatcherServlet customisations: ────────
spring:
  mvc:
    servlet:
      path: /api          # map DispatcherServlet to /api/* instead of /*
    throw-exception-if-no-handler-found: true  # 404 → NoHandlerFoundException
                                               # catch in @RestControllerAdvice
  web:
    resources:
      add-mappings: false   # disable static resource serving (REST-only apps)

// ── @RestControllerAdvice for 404 when throw-exception is enabled: ────
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(NoHandlerFoundException.class)
    public ResponseEntity<ErrorResponse> handleNoHandler(
            NoHandlerFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(ErrorResponse.of(404, "Not Found",
                "No handler for " + ex.getHttpMethod() + " " + ex.getRequestURL()));
    }
}

// ── DispatcherServletRegistrationBean — full control over registration: ─
@Configuration
public class DispatcherConfig {

    @Bean
    public DispatcherServletRegistrationBean dispatcherRegistration(
            DispatcherServlet dispatcherServlet) {
        DispatcherServletRegistrationBean reg =
            new DispatcherServletRegistrationBean(dispatcherServlet, "/api/*");
        reg.setName("apiDispatcher");
        reg.setLoadOnStartup(1);
        reg.addInitParameter("detectAllHandlerMappings", "true");
        return reg;
    }
}

// ── Subclassing DispatcherServlet (rare — override a specific behaviour): ─
@Bean
public DispatcherServlet dispatcherServlet() {
    return new DispatcherServlet() {
        @Override
        protected void noHandlerFound(HttpServletRequest request,
                HttpServletResponse response) throws Exception {
            log.warn("No handler: {} {}", request.getMethod(), request.getRequestURI());
            super.noHandlerFound(request, response);
        }
    };
}

Multiple DispatcherServlets

Advanced applications sometimes register more than one DispatcherServlet — for example, one for the public API and one for an internal admin API, each with independent HandlerMappings and security configurations. Each servlet gets its own ApplicationContext child.
Java
@Configuration
public class MultiServletConfig {

    // ── Public API dispatcher — mapped to /api/* ───────────────────────
    @Bean
    public ServletRegistrationBean<DispatcherServlet> apiServlet() {
        AnnotationConfigWebApplicationContext context =
            new AnnotationConfigWebApplicationContext();
        context.register(ApiWebConfig.class);   // loads @RestController beans for API

        DispatcherServlet servlet = new DispatcherServlet(context);
        ServletRegistrationBean<DispatcherServlet> reg =
            new ServletRegistrationBean<>(servlet, "/api/*");
        reg.setName("apiServlet");
        reg.setLoadOnStartup(1);
        return reg;
    }

    // ── Admin dispatcher — mapped to /admin/* ─────────────────────────
    @Bean
    public ServletRegistrationBean<DispatcherServlet> adminServlet() {
        AnnotationConfigWebApplicationContext context =
            new AnnotationConfigWebApplicationContext();
        context.register(AdminWebConfig.class);  // loads admin controllers only

        DispatcherServlet servlet = new DispatcherServlet(context);
        ServletRegistrationBean<DispatcherServlet> reg =
            new ServletRegistrationBean<>(servlet, "/admin/*");
        reg.setName("adminServlet");
        reg.setLoadOnStartup(2);
        return reg;
    }
}

// ── Context configuration classes for each servlet: ───────────────────
@Configuration
@ComponentScan("com.example.api")       // scans only API controllers
@EnableWebMvc
public class ApiWebConfig implements WebMvcConfigurer {
    // API-specific CORS, interceptors, message converters
}

@Configuration
@ComponentScan("com.example.admin")     // scans only admin controllers
@EnableWebMvc
public class AdminWebConfig implements WebMvcConfigurer {
    // Admin-specific security interceptors, message converters
}

// NOTE: for most applications, a single DispatcherServlet with
// Spring Security path-based rules is simpler and preferable.
// Multiple servlets add complexity — use only when isolation is genuinely needed.