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: DEBUGDispatcherServlet 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.