Introduction to Filters
Introduction to Filters
A servlet filter is a reusable component that intercepts every HTTP request before it reaches its target servlet (or JSP) and every HTTP response before it leaves the server. Filters form a pipeline — the filter chain — and each filter decides independently whether to pass the request along, modify it, or stop it entirely. This architecture cleanly separates cross-cutting concerns (logging, authentication checks, compression, CORS headers) from your business servlets.
The Filter Contract
Every filter implements the jakarta.servlet.Filter interface, which declares three methods:
init(FilterConfig config)— called once when the container instantiates the filter. Use it to read init-params or acquire resources.doFilter(ServletRequest request, ServletResponse response, FilterChain chain)— called on every matching request. This is where your logic lives.destroy()— called once before the container removes the filter. Release any resources acquired ininit.
init() and destroy() both have empty default implementations in the interface, so you only override them when you have something meaningful to do. doFilter remains abstract and must always be implemented.
The Filter Chain
The container builds an ordered chain of all filters whose URL pattern matches the current request. When you call chain.doFilter(request, response) inside your filter, control passes to the next filter in the chain (or to the target servlet if no more filters remain). You can run logic before that call (pre-processing) and logic after it returns (post-processing). If you never call chain.doFilter you short-circuit the chain — the target servlet is never invoked. This is the mechanism authentication filters use to block unauthenticated requests.
Notice the structure: everything before chain.doFilter runs on the way in, everything after runs on the way out. The target servlet executes inside that gap.
Registering a Filter with @WebFilter
The @WebFilter annotation (available since Servlet 3.0) is the modern, zero-XML way to declare a filter. Key attributes:
value/urlPatterns— URL patterns this filter applies to. Supports wildcards:"/api/*","*.json".servletNames— target specific servlet names instead of URL patterns.filterName— override the default name (class name).initParams— pass@WebInitParamkey-value pairs readable ininit(FilterConfig).dispatcherTypes— control whether the filter fires onREQUEST,FORWARD,INCLUDE,ERROR, orASYNCdispatches. Defaults toREQUESTonly.
doFilter signature uses the base ServletRequest/ServletResponse types (which also cover non-HTTP protocols), but in a web application the objects are always HTTP variants. Cast early and assign to a local variable rather than casting repeatedly.
Filter Ordering
When multiple filters match the same URL, the container must pick an order. The rule is:
- Annotation-registered filters (
@WebFilter): the Servlet specification does not guarantee any order among them. The order is implementation-dependent. web.xml-registered filters: execute in the order their<filter-mapping>elements appear in the file.- Mixed: XML-declared filters run first, then annotation-declared filters in undefined order.
If ordering matters — and for security filters it almost always does — declare your filters in web.xml or use a ServletContextListener / ProgrammaticRegistration approach via ServletContext.addFilter().
In this configuration TimingFilter wraps AuthFilter: the request enters TimingFilter, then (if the URL matches) AuthFilter, then the target servlet. The response unwinds in reverse — servlet, AuthFilter post, TimingFilter post.
Wrapping the Request or Response
Filters can not only inspect but also replace the request or response objects by wrapping them. The API provides HttpServletRequestWrapper and HttpServletResponseWrapper for this purpose. A common use-case is capturing the response body (for logging or compression) by substituting a wrapper that buffers output before forwarding it downstream.
chain.doFilter(). If your filter conditionally skips it (e.g., only on auth failure), make sure the else-branch still writes a complete response (redirect, error status, or body). Returning from doFilter without calling the chain and without writing a response leaves the client hanging with an empty 200.
When to Use Filters vs. Interceptors
Filters are a Servlet API feature and operate at the HTTP layer — they see raw requests and responses. Spring MVC provides its own HandlerInterceptor that operates at the dispatcher layer, after Spring has resolved the handler method. Choose filters for concerns that are truly transport-level (security headers, encoding, logging) and interceptors for concerns tied to Spring's request lifecycle (authentication with Spring Security, audit logging tied to controller names, locale resolution). Both mechanisms are complementary and commonly used together in production.
Summary
The Filter interface and the filter chain are the Servlet API's answer to cross-cutting concerns. Implement doFilter, call chain.doFilter to pass control forward, and place logic before or after that call for pre/post processing. Use @WebFilter for simple cases and web.xml (or programmatic registration) when ordering is critical. The next lesson builds on this foundation with practical, production-grade filter implementations.