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.

Java Tech Enthusiast
Java Tech Enthusiast
Java Tech Enthusiast
Spring Boot Interceptor vs Filter: 5 Pitfalls and Choosing the Right One for Login Checks

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", "*"); // ineffective

Correct 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 logic

Complete 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaSpring BootinterceptorSpring MVCfilterLogin Validation
Java Tech Enthusiast
Written by

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!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.