Servlet Filters vs Spring MVC Interceptors: Distinctions and Execution Flow
Core Distinctions
- Interceptors leverage Java reflection, whereas Filters rely on function callbacks.
- Filters are tightly coupled with the Servlet container, while Interceptors are independent of it and exist with in the Spring framework.
- Filters can intercept almost any request type (including static resources), but Interceptors only target requests handled by the DispatcherServlet.
- Interceptors have access to the Spring MVC context and handler-specific objects like
ModelAndView, which Filters cannot access. - Interceptors can seamlessly inject and interact with Spring-managed beans (e.g., Service layers) via IoC, whereas Filters require explicit integration with the Spring application context.
- Filters are instantiated once by the container and reused across requests, whereas Interceptors are managed by Spring and can be invoked multiple times during the request processing lifecycle.
Configuration & Implementation
Interceptors are registered within the Spring MVC configuration, specifying included and excluded path patterns:
@Configuration
public class WebInterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SecurityInterceptor())
.addPathPatterns("/api/items/**", "/api/accounts/**")
.excludePathPatterns("/api/accounts/login");
}
}
Filters operate at the Servlet container level, executing logic before and after the request passes through the filter chain:
public class RequestLoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Filter: Incoming request logged");
filterChain.doFilter(req, res);
System.out.println("Filter: Outgoing response logged");
}
}
Interceptors wrap around the actual handler execution, providing distinct callback points:
public class SecurityInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {
System.out.println("Interceptor: preHandle execution");
return true;
}
@Override
public void postHandle(HttpServletRequest req, HttpServletResponse res, Object handler, ModelAndView mav) throws Exception {
System.out.println("Interceptor: postHandle execution");
}
@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse res, Object handler, Exception ex) throws Exception {
System.out.println("Interceptor: afterCompletion execution");
}
}
Execution Flow and Nesting
Filters encapsulate Interceptors. The filterChain.doFilter(req, res) invocation acts as the boundary. When this method is called, control transitions into the Servlet container, eventually reaching the DispatcherServlet, which triggers the Interceptor chain.
preHandleexecutes after the initial Filter logic but before the target Controller method is invoked.postHandleexecutes after the Controler method returns but before the view is rendered, alowing modifications to theModelAndView.afterCompletionexecutes after view rendering is complete, but still within the boundary offilterChain.doFilter().- Once
afterCompletionfinishes, the DispatcherServlet completes its service, and the call stack returns to thefilterChain.doFilter()line in the Filter, executing the remaining post-processing logic.
The overall sequence is: Filter pre-processing -> DispatcherServlet -> Interceptor preHandle -> Controller -> Interceptor postHandle -> View Rendering -> Interceptor afterCompletion -> DispatcherServlet exit -> Filter post-processing.