Compare Servlet Filters with Spring HandlerInterceptors as implementations of Chain of Responsibility. When would you choose each?
answer
- Filter = servlet container, before DispatcherServlet, sees everything
- Interceptor = inside Spring MVC, knows the handler + ModelAndView
- Only filters can wrap/replace request/response
- Short-circuit: omit chain.doFilter vs. preHandle returns false
- Altitude rule: global/raw → filter; handler/model-aware → interceptor
basics
~20 sBoth let you run code around a request in a chain. Filters live in the servlet container and see every request (even static files), working with raw request/response. Spring HandlerInterceptors live inside Spring MVC and know which controller/handler will run. Use filters for low-level/global concerns, interceptors for MVC-aware logic.
solid answer
~50 sServlet Filters and Spring HandlerInterceptors are both Chain-of-Responsibility interception layers, but at different altitudes. A Filter runs in the servlet container, outside Spring's DispatcherServlet, so it sees every request to the app (including static resources and requests that never reach a controller) and works with the raw ServletRequest/ServletResponse — ideal for global, framework-agnostic concerns: security gates, CORS, compression, request wrapping, correlation ids. A HandlerInterceptor runs inside Spring MVC after the DispatcherServlet has resolved which handler (controller method) applies; it exposes preHandle/postHandle/afterCompletion and has access to the chosen handler and the ModelAndView, so it is better for MVC-aware concerns: per-controller auth annotations, modifying the model, view-level timing. Filters are coarser and earlier; interceptors are finer and Spring-coupled. Choose a filter when you need to act before Spring, on all requests, or to wrap the request/response; choose an interceptor when you need the resolved handler or MVC artifacts.
go deeper
Knows both run code around requests; filters are lower-level/container, interceptors are Spring MVC.
Can list concrete use-cases for each and knows interceptors expose preHandle/postHandle/afterCompletion while filters use doFilter.
Explains the lifecycle positions (filter before DispatcherServlet, interceptor after handler resolution), wrapping being filter-only, and chooses the right layer per concern.
Designs the full interception stack across filters, interceptors, and AOP by altitude and context, avoids cross-layer duplication and ordering coupling, and reasons about async/error-dispatch behavior.
### Both are Chain of Responsibility A **Servlet Filter** chain and a **Spring `HandlerInterceptor`** chain are both ordered sequences of handlers that wrap a request, each able to do work before and after the core processing and to short-circuit. They differ in **where in the request lifecycle they sit** and **what context they have**. ### The request lifecycle (where each sits) 1. Request enters the **servlet container** (Tomcat). 2. The container runs the **Filter chain** (Chain of Responsibility), outermost to innermost. 3. The chain reaches Spring's **`DispatcherServlet`** (the single servlet Spring MVC registers). 4. The DispatcherServlet **resolves the handler** (which `@Controller` method matches the URL) and builds a **`HandlerExecutionChain`** = the handler + its applicable **interceptors**. 5. It runs `preHandle` of each interceptor (in order), invokes the handler, then `postHandle` (reverse order), renders the view, then `afterCompletion` (reverse order). 6. Control unwinds back out through the filter chain. So **filters are strictly outside and earlier**; interceptors are **inside Spring MVC and later**, after the handler is known. ### Key terms - **`DispatcherServlet`**: Spring MVC's front controller — one servlet that dispatches to controllers. Filters run *before* it; interceptors run *within* it. - **`HandlerInterceptor`**: a Spring interface with `preHandle(req,res,handler)` (return false to short-circuit), `postHandle(req,res,handler,modelAndView)` (after the handler, before view render), and `afterCompletion(req,res,handler,ex)` (after render, always — like a finally). - **`HandlerExecutionChain`**: the resolved handler plus its interceptors. - **Raw vs. resolved context**: filters have the raw HTTP request/response only; interceptors additionally know the **handler** that will run and (in postHandle) the **ModelAndView**. ### Capability comparison | Aspect | Servlet Filter | HandlerInterceptor | |---|---|---| | Layer | Servlet container (Jakarta), pre-Spring | Inside Spring MVC | | Sees | Every request incl. static, non-MVC, errors | Only requests routed to a Spring handler | | Context | Raw ServletRequest/Response | Resolved handler + ModelAndView | | Can wrap request/response | Yes (return a wrapper to downstream) | No (cannot replace the request object for the handler) | | Short-circuit | Omit chain.doFilter | preHandle returns false | | Portability | Framework-agnostic (any servlet app) | Spring-specific | | Typical use | Security, CORS, compression, correlation id, encoding, wrapping | Per-controller auth, model enrichment, MVC timing, locale/theme | ### When to choose which - **Choose a Filter when** you must act **before Spring**, on **all** requests (including static assets, error dispatches, requests that never reach a controller), or you need to **wrap/replace** the request or response object (e.g. cache the body, gzip, set encoding) — wrapping must happen in a filter because downstream sees the wrapper. Security and CORS are usually filters so they protect everything uniformly. Spring Security itself is implemented as a filter. - **Choose an interceptor when** the logic needs to know **which handler** runs (e.g. read a method-level annotation), needs the **ModelAndView** to enrich or inspect the model, or is naturally **MVC-scoped** (locale/theme resolution, controller-level timing). Interceptors are also easier to map to specific URL patterns *within* Spring's configuration. ### Edge considerations - **Error handling**: filters wrap error dispatches too; interceptors' `afterCompletion` runs even on handler exceptions (passed the exception), making it a clean place for MVC-level cleanup. - **Async**: both have async caveats — filters with `AsyncContext`, interceptors via `AsyncHandlerInterceptor.afterConcurrentHandlingStarted`. - **Don't double-implement**: putting the same concern in both layers (e.g. auth) causes confusing ordering and duplicated logic; pick the layer that matches the concern's altitude. - **AOP is a third option** for method-level cross-cutting concerns *inside* services (transactions, retries), but it is not request-scoped like these two. ### Principal-level synthesis The decision is fundamentally about **altitude and context**: how early must the concern act, and does it need framework knowledge? Global, framework-agnostic, request-wrapping → filter. Handler-aware, model-aware, Spring-scoped → interceptor. Method-aware, service-internal → AOP. Mapping each concern to the right layer keeps the interception stack understandable and avoids order-coupling bugs.
- Why is request/response wrapping done in a filter rather than an interceptor?A filter can pass a wrapped request/response object as the arguments to chain.doFilter, so all downstream (including the DispatcherServlet and controller) sees the wrapper. An interceptor runs after the request object is already fixed for the handler, so it cannot substitute it.
- How does each layer short-circuit the request?A filter omits the chain.doFilter call and writes the response itself; a HandlerInterceptor returns false from preHandle, which stops the handler from being invoked.
- Where does Spring Security fit?It is a servlet Filter (a DelegatingFilterProxy delegating to a FilterChainProxy that runs its own ordered security-filter sub-chain), so it protects all requests before Spring MVC dispatch.
saying these in an interview costs you the question
- Saying interceptors run before filters (they run inside, after the DispatcherServlet)
- Claiming interceptors see static-resource requests (they don't, unless routed through MVC)
- Trying to wrap the request in an interceptor for the handler (filters do wrapping)
- Implementing the same concern in both layers