When implementing request logging or authentication checks in Spring Boot, you may find yourself unsure whether to use a Filter or an Interceptor. Both serve the same general purpose — inserting common processing before and after a request — but they differ in when they execute and what information they can access.
This article clarifies the technical differences between Filter and HandlerInterceptor, and provides clear selection criteria for common real-world use cases such as authentication, logging, and CORS.
The Basic Roles of Filter and Interceptor
Filter is a mechanism defined in the Java Servlet specification and operates at the servlet container level. Interceptor, on the other hand, is a Spring MVC-specific mechanism that operates within the Spring context.
Both are means of implementing cross-cutting concerns — inserting common processing before and after a request — but because they operate at different layers, choosing the right one for the job is important.
Differences in Execution Timing
The most important difference between Filter and Interceptor is when they execute.
Request → Filter → DispatcherServlet → Interceptor → Controller → Interceptor → DispatcherServlet → Filter → Response
Because Filter executes before the DispatcherServlet, it can modify a request before Spring MVC processing begins, or block a request before it ever reaches Spring MVC.
Interceptor executes after the DispatcherServlet has received the request, immediately before and after a Controller method is called. This allows it to access Controller-specific information.
Differences in Spring DI Container Management
Because Interceptor is a Spring-managed Bean, you can easily inject other Beans into it using @Autowired.
@Component
public class LoggingInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService; // Bean injection is natural here
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
// Processing using userService
return true;
}
}
Filters can also have Beans injected by annotating them with @Component, but they are fundamentally managed by the servlet container. Spring Boot automatically registers Filters as Beans, but if you need explicit control, use FilterRegistrationBean.
Differences in Accessible Information
A Filter can only access HttpServletRequest and HttpServletResponse.
An Interceptor has access to the HandlerMethod object, allowing it to retrieve information about the Controller method being invoked. It can work with annotation metadata and method parameter types, enabling more flexible processing.
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
// Retrieve annotation information from the method
if (method.isAnnotationPresent(RequireAuth.class)) {
// Authentication check logic
}
}
return true;
}
Basic Pattern for Implementing a Filter
When implementing a Filter, it is common practice to extend OncePerRequestFilter, which guarantees the filter runs exactly once per request.
@Component
@Order(1)
public class RequestLoggingFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(RequestLoggingFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
long startTime = System.currentTimeMillis();
try {
filterChain.doFilter(request, response);
} finally {
long duration = System.currentTimeMillis() - startTime;
logger.info("{} {} - {}ms", request.getMethod(), request.getRequestURI(), duration);
}
}
}
The @Order annotation controls execution order. Lower numbers execute first.
Basic Pattern for Implementing an Interceptor
An Interceptor implements the HandlerInterceptor interface.
@Component
public class ExecutionTimeInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(ExecutionTimeInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
request.setAttribute("startTime", System.currentTimeMillis());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
long startTime = (Long) request.getAttribute("startTime");
long duration = System.currentTimeMillis() - startTime;
logger.info("Controller execution time: {}ms", duration);
}
}
To register an Interceptor, implement WebMvcConfigurer.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private ExecutionTimeInterceptor executionTimeInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(executionTimeInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/public/**");
}
}
addPathPatterns and excludePathPatterns let you control which URLs the Interceptor applies to.
Implementing Request/Response Logging
Use a Filter when you want to log all requests. This also captures requests that never reach the DispatcherServlet (such as 404s).
Use an Interceptor when you want per-Controller-method logging. You can record which Controller method was invoked.
When you need to log the request or response body, use ContentCachingRequestWrapper and ContentCachingResponseWrapper.
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class RequestBodyLoggingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);
filterChain.doFilter(wrappedRequest, wrappedResponse);
byte[] requestBody = wrappedRequest.getContentAsByteArray();
byte[] responseBody = wrappedResponse.getContentAsByteArray();
// Log output processing
wrappedResponse.copyBodyToResponse(); // Important: copy the response back before returning
}
}
For detailed logging configuration, see the Spring Boot Logging Configuration Guide with Logback and SLF4J.
Implementing Authentication Checks
When using Spring Security, the standard approach is to delegate authentication to the SecurityFilterChain.
If you are implementing custom token-based authentication without Spring Security, implement it in a Filter, since every request must be authenticated.
If you want to check specific annotations before a Controller is invoked to verify permissions, an Interceptor is the right choice.
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
RequireAdmin annotation = handlerMethod.getMethodAnnotation(RequireAdmin.class);
if (annotation != null) {
// Admin permission check
if (!isAdmin(request)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return false;
}
}
}
return true;
}
Basic usage of Spring Security is covered in Implementing Basic Authentication in Spring Boot.
Implementing CORS Configuration
CORS configuration should use the standard mechanisms provided by Spring. Avoid implementing it manually in a Filter.
The simplest approach is the @CrossOrigin annotation.
@RestController
@CrossOrigin(origins = "http://localhost:3000")
public class ApiController {
// ...
}
For application-wide configuration, use WebMvcConfigurer.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE");
}
}
Implementing Exception Handling
The standard approach for handling exceptions thrown within a Controller is to use @ControllerAdvice. If you want to detect exceptions in an Interceptor, check the Exception parameter in the afterCompletion method.
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
if (ex != null) {
logger.error("Exception occurred during Controller execution", ex);
}
}
When an exception occurs inside a Filter, either handle it within that Filter or delegate it to Spring’s ErrorController.
For detailed exception handling patterns, see REST API Exception Handling in Spring Boot.
Controlling Execution Order
When using multiple Filters, specify their order with the @Order annotation.
@Component
@Order(1)
public class FirstFilter extends OncePerRequestFilter { /* ... */ }
@Component
@Order(2)
public class SecondFilter extends OncePerRequestFilter { /* ... */ }
For more explicit control, use FilterRegistrationBean.
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<RequestLoggingFilter> loggingFilter() {
FilterRegistrationBean<RequestLoggingFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new RequestLoggingFilter());
registrationBean.setOrder(1);
registrationBean.addUrlPatterns("/api/*");
return registrationBean;
}
}
The execution order of Interceptors is determined by the order in which addInterceptor is called.
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(firstInterceptor); // Executes first
registry.addInterceptor(secondInterceptor); // Executes second
}
Decision Criteria for Choosing Between the Two
Here is a summary of decision criteria for when you are unsure in a real-world scenario.
When to choose Filter
- Processing is required before the request reaches Spring MVC
- You need to handle all requests, including those that result in 404s
- You need servlet-specification features such as request wrappers
When to choose Interceptor
- You need access to Controller method information
- You want dynamic processing based on annotation metadata
- You have many dependencies on Spring Beans
- You need fine-grained control over which URLs it applies to
When in doubt, prefer Interceptor. It integrates more naturally with the Spring ecosystem and is easier to test.
If you need more advanced cross-cutting concern implementations, consider using AOP. It is covered in detail in What is AOP in Spring Boot.
Summary
Filter and Interceptor differ in when they execute and what information they can access. Filter operates at the servlet level; Interceptor operates at the Spring MVC level.
By choosing the right tool for each feature — whether it is authentication checks, request logging, or something else — you can write more maintainable code. As a general rule, prefer Interceptor first, and reach for Filter only when you need to process a request before it reaches the DispatcherServlet.