Spring Boot Interceptor vs Filter: 5 Pitfalls and Choosing the Right One for Login Checks
Spring Boot’s Filter and Interceptor can both intercept requests, but developers often misuse them; this article explains five frequent traps—null autowiring in Filters, execution order mistakes, @Value timing issues in Interceptors, Swagger blocking, and exception handling—offering concrete fixes and a decision guide.
Problem 1 – @Autowired in a Filter is always null
Filters are instantiated by the Servlet container (e.g., Tomcat) and are not managed by the Spring IoC container, so field injection such as @Autowired private UserService userService; never occurs, leading to a NullPointerException when the service is used.
@Component
public class TokenFilter implements Filter {
@Autowired
private UserService userService; // always null
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
String token = ((HttpServletRequest) req).getHeader("Authorization");
// userService.findByToken(token); // NPE
chain.doFilter(req, resp);
}
}Fix 1 – Register the filter with Spring so that dependencies can be injected
public class TokenFilter implements Filter {
private final UserService userService;
public TokenFilter(UserService userService) { this.userService = userService; }
// ... doFilter implementation ...
}
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<TokenFilter> tokenFilter(UserService userService) {
FilterRegistrationBean<TokenFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new TokenFilter(userService));
bean.addUrlPatterns("/api/*");
bean.setOrder(1);
return bean;
}
}Fix 2 – Use a Spring‑managed Interceptor instead of a Filter
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
User user = userService.findByToken(token);
if (user == null) {
throw new BusinessException(401, "未登录");
}
return true;
}
}Recommendation: Prefer an Interceptor when Spring bean injection is required; use a Filter only when you must intercept every request, including static resources.
Problem 2 – Setting response headers after chain.doFilter has no effect
When a Filter adds CORS or other response headers after delegating to the next filter, the response may already be committed, so the headers are ignored.
// Incorrect
chain.doFilter(req, resp);
resp.setHeader("Access-Control-Allow-Origin", "*"); // ineffectiveCorrect approach – set headers before delegating
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) resp;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
chain.doFilter(req, resp);
}Note: Filter pre‑processing runs before Interceptor pre‑handle; filter post‑processing runs after Interceptor post‑handle.
Problem 3 – @Value is null in an Interceptor constructor
During construction Spring has not performed field injection yet, so reading a @Value field inside the constructor yields null.
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Value("${auth.whitelist}")
private List<String> whitelist; // null in constructor
public AuthInterceptor() { /* injection not ready */ }
}Fix – initialise after injection
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Value("${auth.whitelist}")
private List<String> whitelist;
private Set<String> whitelistSet;
@PostConstruct
public void init() {
whitelistSet = new HashSet<>(whitelist);
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// use whitelistSet here
return true;
}
}Interceptor is a singleton; @PostConstruct runs only once. For dynamic updates combine with Spring Cloud @RefreshScope .
Problem 4 – Interceptor blocks Swagger/Knife4j documentation
After registering a login Interceptor, requests to /doc.html or Swagger UI return 401 because the paths are not excluded.
Solution – exclude static resources and documentation paths
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private TokenInterceptor tokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns(
"/api/user/login",
"/api/user/register",
"/doc.html",
"/swagger-ui/**",
"/v3/api-docs/**",
"/webjars/**",
"/error");
}
}Optionally annotate controller methods with a custom @NoLogin annotation and also add those methods to excludePathPatterns for double safety.
Problem 5 – Throwing exceptions from a Filter bypasses global exception handlers
Exceptions thrown inside a Filter occur before the request reaches DispatcherServlet, so @RestControllerAdvice cannot catch them. The container returns its default 500 page.
Fix – write the error response directly in the Filter
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String token = request.getHeader("Authorization");
if (token == null || token.isEmpty()) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":401,\"message\":\"未登录\",\"data\":null}");
return; // stop chain
}
chain.doFilter(req, resp);
}A utility method can encapsulate the JSON writing:
public class WebUtils {
public static void writeJson(HttpServletResponse response, int status, Result<?> result) throws IOException {
response.setStatus(status);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(new ObjectMapper().writeValueAsString(result));
}
}Rule of thumb: Filters should only perform interception and response writing; complex business logic belongs in Interceptors or AOP.
Filter vs Interceptor – comparison
Ownership : Filter – Servlet container; Interceptor – Spring container.
Dependency injection : Filter needs explicit configuration (e.g., FilterRegistrationBean); Interceptor supports @Autowired natively.
Execution order : Filter runs before Interceptor for pre‑processing and after Interceptor for post‑processing.
Scope of interception : Filter can intercept all requests, including static resources; Interceptor only intercepts controller‑mapped requests.
Exception handling : Exceptions from Filter are not caught by @RestControllerAdvice; Interceptor exceptions are handled by Spring’s global exception handlers.
Typical use cases : Filter – CORS, XSS, low‑level request/response manipulation; Interceptor – authentication, authorization, logging, request‑level business checks.
One‑sentence selection guide
Need to intercept every request (including static resources) → Filter
Need Spring bean injection → Interceptor
Need unified global exception handling → Interceptor
Both needed → Use Filter for basic interception + Interceptor for business logicComplete configuration templates
TokenInterceptor.java
@Component
@Slf4j
public class TokenInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
@Value("${auth.whitelist}")
private List<String> whitelistPaths;
private Set<String> whitelistSet;
@PostConstruct
public void init() { whitelistSet = new HashSet<>(whitelistPaths); }
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. whitelist pass
if (whitelistSet.contains(request.getRequestURI())) { return true; }
// 2. @NoLogin annotation pass
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
if (hm.hasMethodAnnotation(NoLogin.class)) { return true; }
}
// 3. token validation
String token = request.getHeader("Authorization");
if (token == null || token.isEmpty()) {
throw new BusinessException(401, "未登录");
}
User user = userService.findByToken(token);
if (user == null) {
throw new BusinessException(401, "Token 已过期");
}
UserContext.set(user);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
UserContext.clear(); // prevent memory leak
}
}WebMvcConfig.java
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private TokenInterceptor tokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns(
"/api/user/login",
"/api/user/register",
"/doc.html",
"/swagger-ui/**",
"/v3/api-docs/**",
"/webjars/**",
"/error")
.order(1);
}
}UserContext.java (ThreadLocal holder)
public class UserContext {
private static final ThreadLocal<User> USER_HOLDER = new ThreadLocal<>();
public static void set(User user) { USER_HOLDER.set(user); }
public static User get() { return USER_HOLDER.get(); }
public static void clear() { USER_HOLDER.remove(); }
}NoLogin.java (annotation to skip login)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoLogin {}These snippets constitute a robust setup that avoids the five common pitfalls described above.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Tech Enthusiast
Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
