
Java Servlet Filter Example Tutorial
Java Servlet Filters represent one of the most powerful yet underutilized features in web application development, acting as interceptors that can modify requests and responses before they reach your servlets or after they leave. These components enable cross-cutting concerns like authentication, logging, data compression, and security enforcement without cluttering your core business logic. In this tutorial, you’ll learn how to create, configure, and deploy servlet filters effectively, understand their lifecycle, explore practical implementation patterns, and avoid common pitfalls that can impact performance and functionality.
How Servlet Filters Work
Servlet filters operate on the interceptor pattern, forming a chain of processing components between the client request and your servlet. When a request arrives, it passes through each filter in the chain before reaching the target servlet. Similarly, the response travels back through the same filters in reverse order.
The filter chain mechanism allows multiple filters to process the same request, with each filter having the opportunity to:
- Examine and modify request headers, parameters, and content
- Block requests that don’t meet specific criteria
- Add or modify response headers and content
- Log request and response information
- Measure processing time and performance metrics
Every filter must implement the javax.servlet.Filter
interface, which defines three essential methods:
public interface Filter {
void init(FilterConfig config) throws ServletException;
void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
void destroy();
}
Step-by-Step Filter Implementation Guide
Let’s start with a basic logging filter that demonstrates the fundamental concepts. This filter will log incoming requests and measure response times.
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.logging.Logger;
public class RequestLoggingFilter implements Filter {
private static final Logger logger = Logger.getLogger(RequestLoggingFilter.class.getName());
private FilterConfig filterConfig;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
logger.info("RequestLoggingFilter initialized");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
long startTime = System.currentTimeMillis();
String requestURI = httpRequest.getRequestURI();
String method = httpRequest.getMethod();
String clientIP = httpRequest.getRemoteAddr();
logger.info(String.format("Incoming request: %s %s from %s",
method, requestURI, clientIP));
try {
// Continue the filter chain
chain.doFilter(request, response);
} finally {
long duration = System.currentTimeMillis() - startTime;
int status = httpResponse.getStatus();
logger.info(String.format("Request completed: %s %s - Status: %d - Duration: %dms",
method, requestURI, status, duration));
}
}
@Override
public void destroy() {
logger.info("RequestLoggingFilter destroyed");
filterConfig = null;
}
}
Next, configure the filter in your web.xml
file:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<filter>
<filter-name>RequestLoggingFilter</filter-name>
<filter-class>com.example.RequestLoggingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>RequestLoggingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
For applications using Servlet 3.0+ annotations, you can simplify configuration:
import javax.servlet.annotation.WebFilter;
@WebFilter(urlPatterns = {"/*"}, filterName = "RequestLoggingFilter")
public class RequestLoggingFilter implements Filter {
// Implementation remains the same
}
Real-World Examples and Use Cases
Let’s explore several practical filter implementations that address common web application requirements.
Authentication Filter
This filter ensures users are authenticated before accessing protected resources:
@WebFilter(urlPatterns = {"/admin/*", "/user/*"})
public class AuthenticationFilter implements Filter {
private Set<String> publicPaths;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
publicPaths = new HashSet<>();
publicPaths.add("/login");
publicPaths.add("/register");
publicPaths.add("/public");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpSession session = httpRequest.getSession(false);
String requestURI = httpRequest.getRequestURI();
String contextPath = httpRequest.getContextPath();
String path = requestURI.substring(contextPath.length());
boolean isAuthenticated = (session != null && session.getAttribute("user") != null);
boolean isPublicPath = publicPaths.stream().anyMatch(path::startsWith);
if (isAuthenticated || isPublicPath) {
chain.doFilter(request, response);
} else {
httpResponse.sendRedirect(contextPath + "/login");
}
}
@Override
public void destroy() {
publicPaths = null;
}
}
CORS Filter
Essential for modern web applications that serve APIs to different domains:
@WebFilter(urlPatterns = {"/api/*"})
public class CORSFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// Set CORS headers
httpResponse.setHeader("Access-Control-Allow-Origin", "*");
httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
httpResponse.setHeader("Access-Control-Max-Age", "3600");
httpResponse.setHeader("Access-Control-Allow-Headers",
"Content-Type, Authorization, X-Requested-With");
// Handle preflight requests
if ("OPTIONS".equalsIgnoreCase(httpRequest.getMethod())) {
httpResponse.setStatus(HttpServletResponse.SC_OK);
return;
}
chain.doFilter(request, response);
}
}
Response Compression Filter
Reduces bandwidth usage by compressing response content:
@WebFilter(urlPatterns = {"*.html", "*.css", "*.js", "/api/*"})
public class CompressionFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String acceptEncoding = httpRequest.getHeader("Accept-Encoding");
if (acceptEncoding != null && acceptEncoding.contains("gzip")) {
GzipResponseWrapper wrappedResponse = new GzipResponseWrapper(httpResponse);
chain.doFilter(request, wrappedResponse);
wrappedResponse.finishResponse();
} else {
chain.doFilter(request, response);
}
}
}
Filter Configuration and Ordering
Filter execution order matters significantly. Filters declared first in web.xml
execute first in the request chain and last in the response chain. Here’s how different configuration methods handle ordering:
Configuration Method | Ordering Control | Flexibility | Best Use Case |
---|---|---|---|
web.xml | Declaration order | High | Complex applications with many filters |
@WebFilter | Alphabetical by class name | Low | Simple applications with few filters |
ServletContext.addFilter() | Registration order | High | Dynamic filter registration |
For programmatic filter registration, use a ServletContextListener
:
@WebListener
public class FilterRegistrationListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
// Register filters in specific order
FilterRegistration.Dynamic corsFilter =
context.addFilter("CORSFilter", new CORSFilter());
corsFilter.addMappingForUrlPatterns(null, false, "/api/*");
FilterRegistration.Dynamic authFilter =
context.addFilter("AuthFilter", new AuthenticationFilter());
authFilter.addMappingForUrlPatterns(null, false, "/admin/*");
FilterRegistration.Dynamic loggingFilter =
context.addFilter("LoggingFilter", new RequestLoggingFilter());
loggingFilter.addMappingForUrlPatterns(null, false, "/*");
}
}
Performance Considerations and Best Practices
Filter performance directly impacts your application’s response times. Here are key optimization strategies:
- Minimize filter scope: Apply filters only to URLs that actually need them rather than using /* for everything
- Fail fast: Perform expensive operations only when necessary, and exit early when possible
- Cache expensive computations: Store results of costly operations in request attributes or session
- Avoid blocking operations: Don’t perform database queries or external API calls unless absolutely necessary
- Use appropriate data structures: HashSet for lookups, ArrayList for iteration
Here’s a performance-optimized authentication filter:
@WebFilter(urlPatterns = {"/admin/*"})
public class OptimizedAuthFilter implements Filter {
private static final Set<String> PUBLIC_PATHS = Collections.unmodifiableSet(
new HashSet<>(Arrays.asList("/login", "/register", "/public"))
);
private static final String USER_ATTR = "authenticated_user";
private static final String AUTH_CHECK_ATTR = "auth_checked";
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// Check if authentication was already verified in this request
if (httpRequest.getAttribute(AUTH_CHECK_ATTR) != null) {
chain.doFilter(request, response);
return;
}
String path = getRelativePath(httpRequest);
// Quick exit for public paths
if (isPublicPath(path)) {
httpRequest.setAttribute(AUTH_CHECK_ATTR, Boolean.TRUE);
chain.doFilter(request, response);
return;
}
// Check authentication
HttpSession session = httpRequest.getSession(false);
boolean isAuthenticated = (session != null && session.getAttribute(USER_ATTR) != null);
httpRequest.setAttribute(AUTH_CHECK_ATTR, Boolean.TRUE);
if (isAuthenticated) {
chain.doFilter(request, response);
} else {
httpResponse.sendRedirect(httpRequest.getContextPath() + "/login");
}
}
private String getRelativePath(HttpServletRequest request) {
String requestURI = request.getRequestURI();
String contextPath = request.getContextPath();
return requestURI.substring(contextPath.length());
}
private boolean isPublicPath(String path) {
return PUBLIC_PATHS.stream().anyMatch(path::startsWith);
}
}
Common Pitfalls and Troubleshooting
Several issues frequently occur when implementing servlet filters. Here are the most common problems and their solutions:
Forgetting to Call chain.doFilter()
This is the most common mistake that breaks the entire filter chain:
// WRONG - Missing chain.doFilter() call
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// Some processing
if (someCondition) {
response.getWriter().write("Access denied");
return; // This stops the chain completely
}
// Missing: chain.doFilter(request, response);
}
// CORRECT - Always call chain.doFilter() when request should continue
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (someCondition) {
response.getWriter().write("Access denied");
return;
}
chain.doFilter(request, response); // Essential for proper flow
}
Response Already Committed Errors
Attempting to modify response headers or redirect after the response has been written:
// WRONG - Trying to modify response after chain execution
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("X-Custom-Header", "value"); // May fail if response committed
}
// CORRECT - Modify response before chain execution
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
if (!httpResponse.isCommitted()) {
httpResponse.setHeader("X-Custom-Header", "value");
}
chain.doFilter(request, response);
}
Filter Mapping Issues
Incorrect URL patterns can cause filters to not execute or execute too broadly:
<!-- Common mapping patterns and their effects -->
<filter-mapping>
<filter-name>AuthFilter</filter-name>
<url-pattern>/admin</url-pattern> <!-- Only matches exact /admin -->
</filter-mapping>
<filter-mapping>
<filter-name>AuthFilter</filter-name>
<url-pattern>/admin/*</url-pattern> <!-- Matches /admin/ and subdirectories -->
</filter-mapping>
<filter-mapping>
<filter-name>AuthFilter</filter-name>
<url-pattern>*.jsp</url-pattern> <!-- Matches all JSP files -->
</filter-mapping>
Advanced Filter Patterns
For complex applications, consider these advanced implementation patterns:
Conditional Filter Chain
Skip certain filters based on request characteristics:
@WebFilter(urlPatterns = {"/*"})
public class ConditionalFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// Skip processing for static resources
String requestURI = httpRequest.getRequestURI();
if (requestURI.matches(".*\\.(css|js|png|jpg|gif|ico)$")) {
chain.doFilter(request, response);
return;
}
// Skip for health checks
if ("/health".equals(requestURI) || "/status".equals(requestURI)) {
chain.doFilter(request, response);
return;
}
// Apply full processing for other requests
processRequest(httpRequest);
chain.doFilter(request, response);
}
private void processRequest(HttpServletRequest request) {
// Complex processing logic here
}
}
Request/Response Wrapper Pattern
Modify request or response content by wrapping them:
public class RequestModificationFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// Wrap request to modify parameters
HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper((HttpServletRequest) request) {
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if (value != null && "userInput".equals(name)) {
// Sanitize input
return value.replaceAll("[<>\"']", "");
}
return value;
}
};
chain.doFilter(requestWrapper, response);
}
}
Testing Servlet Filters
Testing filters requires proper setup of mock objects and filter chains:
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.*;
public class RequestLoggingFilterTest {
@Mock
private HttpServletRequest request;
@Mock
private HttpServletResponse response;
@Mock
private FilterChain filterChain;
private RequestLoggingFilter filter;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
filter = new RequestLoggingFilter();
}
@Test
public void testFilterLogsRequest() throws Exception {
// Setup
when(request.getRequestURI()).thenReturn("/test");
when(request.getMethod()).thenReturn("GET");
when(request.getRemoteAddr()).thenReturn("127.0.0.1");
when(response.getStatus()).thenReturn(200);
// Execute
filter.doFilter(request, response, filterChain);
// Verify
verify(filterChain).doFilter(request, response);
verify(request).getRequestURI();
verify(request).getMethod();
verify(request).getRemoteAddr();
}
}
When deploying applications with servlet filters, proper server configuration becomes crucial. Whether you’re using a VPS for development and testing or dedicated servers for production environments, ensure your servlet container is properly tuned for filter performance. Monitor filter execution times and consider implementing filter-specific metrics to identify bottlenecks in your application’s request processing pipeline.
Servlet filters provide a clean, maintainable way to implement cross-cutting concerns in Java web applications. By following these patterns and best practices, you’ll create robust, performant filters that enhance your application’s functionality without compromising code quality. Remember to keep filters focused on single responsibilities, test them thoroughly, and monitor their performance impact in production environments.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.