Servlet Filter Basics and History

A servlet filter is a Java component introduced in the Servlet 2.3 specification in 2001. It intercepts HTTP requests before servlets handle them and responses before they are returned to clients, making it useful for preprocessing and postprocessing tasks. This makes it easier to implement cross-cutting concerns such as authentication, logging, character encoding, and data compression in a reusable way separate from business logic. Filters are based on the Chain of Responsibility design pattern, so multiple filters can be linked together and executed in sequence.

Servlet

A servlet is a Java server-side component that processes client HTTP requests and generates HTTP responses. It is a core technology of the Java EE (now Jakarta EE) standard and serves as the foundation for most Java web frameworks.

Primary Use Cases for Filters

Servlet filters are useful when requests or responses need to be modified, or when shared processing should happen before or after request handling. Common use cases include authentication and authorization checks, request and response logging, data encryption and decryption, HTTP header manipulation, image format conversion, data compression and decompression, and custom exception handling. By implementing these concerns as filters, common functionality can be applied without duplicating code across individual servlets or controllers.

Filter Implementation Methods

GenericFilterBean

GenericFilterBean is an abstract class provided by Spring Framework that implements the javax.servlet.Filter interface. It integrates with Spring’s configuration model, allowing bean properties to be applied during filter initialization. Developers override the doFilter method to implement logic that runs for each request.

public class CustomFilter extends GenericFilterBean {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        // Preprocessing logic
        filterChain.doFilter(request, response);
        // Postprocessing logic
    }
}

OncePerRequestFilter

OncePerRequestFilter is a class that extends GenericFilterBean and guarantees execution exactly once per HTTP request. Even if the same request passes through the filter chain multiple times because of a forward or include inside the servlet container, this filter runs its logic only on the first pass. This makes it suitable for tasks that should happen only once per request, such as authentication or logging.

public class CustomFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // Preprocessing logic
        filterChain.doFilter(request, response);
        // Postprocessing logic
    }
}

Filter Chain and Execution Order

When a request arrives, the servlet container executes registered filters sequentially according to the filter chain. After the request passes through all filters, the container invokes the target servlet. When servlet processing finishes, each filter runs its postprocessing logic in reverse order. This structure implements the Chain of Responsibility pattern, allowing each filter to perform its role independently while passing control to the next filter or servlet.

Filter execution order can be specified using the @Order annotation or the setOrder method of FilterRegistrationBean. Lower numbers indicate higher priority and earlier execution.

@Order(1)
public class FirstFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // First filter to execute
        filterChain.doFilter(request, response);
    }
}

Filter Registration Methods

In Spring Boot, filters can be registered in two ways: by using the @Component annotation for automatic registration or by using FilterRegistrationBean for programmatic registration. FilterRegistrationBean provides finer control, such as applying filters only to specific URL patterns or explicitly setting execution order.

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<LoggingFilter> loggingFilter() {
        FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new LoggingFilter());
        registrationBean.addUrlPatterns("/*");
        registrationBean.setOrder(1);
        return registrationBean;
    }
}

Filter vs Spring Interceptor Comparison

Servlet filters and Spring interceptors both intercept requests for processing, but they differ in where they run and when they execute. Filters operate at the servlet container level (Tomcat, Jetty, and similar servers) and run before the request reaches the DispatcherServlet. In contrast, interceptors run within Spring MVC after the DispatcherServlet identifies the handler and before or after controller execution. That gives them easier access to the Spring context and controller-level information.

CharacteristicServlet FilterSpring Interceptor
Execution LayerServlet container level (before DispatcherServlet)Spring MVC level (after DispatcherServlet)
ScopeAll HTTP requestsOnly requests going to Spring MVC controllers
Spring Context AccessLimited (DI possible but requires configuration)Easy (automatic Spring Bean injection)
Exception HandlingServletException, IOExceptionCan use Spring @ExceptionHandler
Typical Use CasesCharacter encoding, CORS, security, loggingAuthentication/authorization, API versioning, logging

Request Processing Flow

Client Request
    ↓
Filter 1 (preprocessing)
    ↓
Filter 2 (preprocessing)
    ↓
DispatcherServlet
    ↓
Interceptor 1 (preHandle)
    ↓
Interceptor 2 (preHandle)
    ↓
Controller
    ↓
Interceptor 2 (postHandle)
    ↓
Interceptor 1 (postHandle)
    ↓
View Rendering
    ↓
Interceptor 2 (afterCompletion)
    ↓
Interceptor 1 (afterCompletion)
    ↓
Filter 2 (postprocessing)
    ↓
Filter 1 (postprocessing)
    ↓
Client Response

Practical Examples

CORS Filter

This filter configures Cross-Origin Resource Sharing (CORS), allowing API calls from different domains under controlled rules instead of having them blocked by the browser’s Same-Origin Policy. It handles preflight requests using the OPTIONS method.

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");

        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            return;
        }

        filterChain.doFilter(request, response);
    }
}

JWT Authentication Filter

This filter validates JSON Web Tokens and sets authentication information in Spring Security’s SecurityContext. It extracts the Bearer token from the Authorization header and verifies its validity.

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader("Authorization");

        if (token != null && token.startsWith("Bearer ")) {
            token = token.substring(7);
            if (jwtUtil.validateToken(token)) {
                String username = jwtUtil.getUsernameFromToken(token);
                UsernamePasswordAuthenticationToken authentication =
                    new UsernamePasswordAuthenticationToken(username, null, Collections.emptyList());
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }

        filterChain.doFilter(request, response);
    }
}

Precautions When Implementing Filters

When implementing filters, keep the following points in mind. First, filter order significantly affects behavior, so it should be managed explicitly using the @Order annotation or the setOrder method. Second, omitting the filterChain.doFilter(request, response) call prevents the request from being forwarded to the next filter or servlet. Third, exceptions raised in filters cannot be handled by Spring’s @ControllerAdvice, so direct try-catch handling is often required inside the filter. Fourth, since filters execute for all requests, heavy operations that could impact performance should be avoided, and URL patterns or conditional checks should be used to run them only when necessary.

Conclusion

Since its introduction in the Servlet 2.3 specification in 2001, servlet filters have remained a core mechanism for handling cross-cutting concerns in Java web applications. Operating at the servlet container level, they can apply common functionality such as authentication, logging, encoding, and CORS configuration to all HTTP requests. The Chain of Responsibility pattern enables flexible request-processing pipelines by combining multiple filters. Understanding the differences between servlet filters and Spring interceptors helps when choosing the right tool for a given web application concern.