What is the difference between a Servlet Filter and a Spring HandlerInterceptor?
answer
- Filter = container/Servlet spec; Interceptor = Spring MVC
- Filter wraps DispatcherServlet; Interceptor inside it
- Interceptor knows the handler; filter does not
- Filter sees static/errors/all servlets
- pre/post/afterCompletion vs doFilter
basics
~10 sA Filter runs in the Servlet container around every request, before Spring even sees it. A HandlerInterceptor runs inside Spring MVC's DispatcherServlet, only for controller requests, and knows which controller (handler) will run.
solid answer
~40 sA servlet Filter (jakarta.servlet.Filter) is a Servlet-spec component that wraps the whole request/response at container level via doFilter(req, res, chain). It runs before and after the DispatcherServlet, sees every request (static files, other servlets, errors), and can short-circuit or wrap the request/response. A HandlerInterceptor is a Spring MVC concept that runs inside the DispatcherServlet, after handler mapping, so it only sees requests routed to a Spring handler and it knows the handler object. It exposes preHandle, postHandle, and afterCompletion. Rule of thumb: use a Filter for low-level, framework-agnostic concerns (logging, compression, CORS, security) and an interceptor when you need Spring MVC context — the target handler, the ModelAndView, or path-pattern matching. Filters are registered with the container; interceptors via WebMvcConfigurer.
code
java · 28 lines// Servlet Filter — container scope, framework-agnostic
@Component
public class RequestLoggingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws ServletException, IOException {
long start = System.nanoTime();
try {
chain.doFilter(req, res); // pass down the chain (into DispatcherServlet)
} finally {
long ms = (System.nanoTime() - start) / 1_000_000;
log.info("{} {} -> {} ({} ms)", req.getMethod(), req.getRequestURI(), res.getStatus(), ms);
}
}
}
// HandlerInterceptor — MVC scope, handler-aware
public class AuditInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
if (handler instanceof HandlerMethod hm) {
// We know the exact controller method that will run
log.info("Invoking {}", hm.getMethod().getName());
}
return true; // false would abort before the controller runs
}
}go deeper
Know the one-line distinction: Filter = container level, runs on everything; Interceptor = Spring MVC level, handler-aware.
Should articulate the wrapping order (filter -> interceptor -> handler) and which registration mechanism each uses.
Should give concrete 'use which' examples and the @ResponseBody / static-resource gotchas.
Frames it as layering: container boundary vs framework boundary, and the consequences for cross-cutting architecture (where security, tracing, and MDC belong).
## The two mechanisms Both are ways to run cross-cutting logic around web request processing, but they live at different layers. ### Servlet Filter `jakarta.servlet.Filter` is part of the **Servlet specification**, not Spring. A filter has one main method: ```java void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) ``` Filters form a **chain**. Each filter does work, then calls `chain.doFilter(req, res)` to pass control to the next filter (and eventually to the `DispatcherServlet`). Code before `chain.doFilter` runs on the way **in**; code after it runs on the way **out**. A filter can: - inspect or modify the request/response, - **wrap** them (`HttpServletRequestWrapper` / `HttpServletResponseWrapper`), - **short-circuit** by not calling `chain.doFilter` (e.g. reject with 401), - catch exceptions bubbling back up. Because it sits at the **container** level, a filter sees **every** request into the web app: requests to `DispatcherServlet`, to other servlets, to static resources, and error dispatches. It has **no idea** which Spring controller will handle the request — Spring hasn't run yet. ### HandlerInterceptor `org.springframework.web.servlet.HandlerInterceptor` is a **Spring MVC** concept. It runs **inside** the `DispatcherServlet`, *after* Spring has decided which handler (controller method) should serve the request. Its three methods: - `boolean preHandle(request, response, handler)` — before the handler runs. Returning `false` stops processing. - `void postHandle(request, response, handler, ModelAndView)` — after the handler, before the view renders. - `void afterCompletion(request, response, handler, Exception)` — after the whole request finishes. Because it runs after handler mapping, it **knows the handler** (often a `HandlerMethod`) and can be scoped to URL patterns. ## Scope difference (key mental model) ``` [Container] Filter -> Filter -> DispatcherServlet [Spring MVC] Interceptor.preHandle -> Controller -> Interceptor.postHandle -> View -> Interceptor.afterCompletion <- back out through the DispatcherServlet <- Filter <- Filter (unwind) ``` A filter **wraps** the interceptor, which wraps the handler. ## When to use which - **Filter**: framework-agnostic, low-level concerns — request/response logging with body caching, GZIP compression, CORS, request ID/MDC setup, authentication (Spring Security itself is a filter chain), rate limiting, anything that must also apply to static resources or non-Spring servlets, or anything needing to modify the raw response bytes. - **HandlerInterceptor**: concerns that need **Spring MVC context** — checking annotations on the target `HandlerMethod`, populating the model, measuring controller execution time, path-pattern-scoped auth checks, adding response headers based on the matched handler. ## Gotchas - A filter can modify the response body by wrapping; an interceptor's `postHandle` cannot usefully change a `@ResponseBody` REST response because the body is already written by the `HttpMessageConverter`. - Interceptors only fire for requests that reach a Spring handler — a 404 for an unmapped path may never hit your interceptor, but a filter still sees it. - Registration differs: filters register with the container (bean auto-registration, `FilterRegistrationBean`, `@WebFilter`); interceptors register via `WebMvcConfigurer.addInterceptors`.
- Which one can see a request for a static resource or a URL with no controller mapping?The Filter. It runs at container level before the DispatcherServlet, so it sees every request including static resources and unmapped paths (404s). A HandlerInterceptor only fires when a request is routed to a Spring handler.
- Can an interceptor modify a JSON @ResponseBody payload in postHandle?Not usefully. For @ResponseBody, the HttpMessageConverter has already serialized and written the body to the response by the time postHandle runs, so mutating the ModelAndView does nothing. To alter the raw bytes you need a Filter with a ContentCachingResponseWrapper (or a ResponseBodyAdvice).
saying these in an interview costs you the question
- Claiming interceptors run before filters
- Saying a HandlerInterceptor sees static resources / all requests
- Thinking Filter and HandlerInterceptor are the same thing with different names
- Believing an interceptor knows nothing about the target handler