Filter vs Inerceptor in Spring
TL;DR
Aspect | Servlet Filter | Spring MVC Interceptor |
---|---|---|
Layer | Servlet container (before DispatcherServlet ) |
Spring MVC (around handler/controller) |
Typical uses | CORS, security pre-checks, logging, request/response wrapping, compression | Auth/ACL per handler, auditing with handler info, adding model attrs, metrics |
Scope | Can apply to all URLs (incl. static, error pages) | Applies to MVC handler mappings (controllers) |
API |
javax.servlet.Filter / OncePerRequestFilter
|
HandlerInterceptor |
Order/Dispatch types |
@Order / FilterRegistrationBean , DispatcherType control |
Registered via WebMvcConfigurer#addInterceptors , path include/exclude |
Body access | Can wrap request/response (e.g., ContentCaching* ) |
Not for rewriting body; mainly metadata & flow control |
Async & errors | Can surround chain.doFilter() and catch errors |
postHandle , afterCompletion , afterConcurrentHandlingStarted
|
Request flow: Filter(s) β DispatcherServlet β Interceptor.preHandle β Controller β Interceptor.postHandle β view β Interceptor.afterCompletion β Filter(s)
.
When to pick which
-
Pick a Filter if you need cross-cutting behavior at the servlet level: CORS, security headers, request/response logging, wrapping the body, handling
DispatcherType.ERROR
, etc. (Spring Security is filter-based.) - Pick an Interceptor if behavior depends on the handler (e.g., which controller/method, annotations, path variables) or you want to add attributes to the model, or block a specific controller call.
Minimal examples
1) Logging Filter (runs once per request)
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
@Order(Ordered.HIGHEST_PRECEDENCE) // before others, if needed
public class RequestLoggingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws ServletException, IOException {
long t0 = System.nanoTime();
try { chain.doFilter(req, res); }
finally {
long ms = (System.nanoTime() - t0) / 1_000_000;
logger.info("{} {} -> {} ({} ms)", req.getMethod(), req.getRequestURI(), res.getStatus(), ms);
}
}
}
Optional fine control (URL patterns, order, dispatcher types):
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
@Bean
FilterRegistrationBean<RequestLoggingFilter> loggingFilterReg(RequestLoggingFilter filter) {
var reg = new FilterRegistrationBean<>(filter);
reg.addUrlPatterns("/*");
reg.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
reg.setOrder(Ordered.LOWEST_PRECEDENCE);
return reg;
}
2) Auth Interceptor (blocks specific controller paths)
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import jakarta.servlet.http.*;
@Component
public class ApiKeyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) return true; // skip non-controller resources
String apiKey = req.getHeader("X-API-KEY");
if (!"secret".equals(apiKey)) {
res.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Missing/invalid API key");
return false; // stop invocation; controller won't run
}
return true;
}
}
Register & scope it to routes:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final ApiKeyInterceptor apiKeyInterceptor;
public WebConfig(ApiKeyInterceptor apiKeyInterceptor) { this.apiKeyInterceptor = apiKeyInterceptor; }
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(apiKeyInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/public/**");
}
}
Extra notes (production-friendly)
-
Reading request/response bodies for logging? Do it in a Filter with
ContentCachingRequestWrapper
/ContentCachingResponseWrapper
to avoid consuming the stream. -
Error pages & forwards: Filters can target
DispatcherType.ERROR/INCLUDE/FORWARD
; Interceptors wonβt see error dispatches. -
Async MVC: Interceptor has
afterConcurrentHandlingStarted
; Filters see async viaDispatcherType.ASYNC
. -
Reactive stack (WebFlux): use
WebFilter
(instead of Filter) andHandlerFilterFunction
(instead of Interceptor).
Quick decision recipe
- Need handler method/annotation info? β Interceptor
- Need to affect all traffic or wrap/inspect raw requests/responses? β Filter
- Using Spring Security? β Prefer security filters over custom interceptors.