Skip to content
Spring Boot sb web 3 min read

Interceptors

A HandlerInterceptor lets you run logic around controller execution — before the handler runs, after it produces a result, and after the response completes. Because interceptors sit inside Spring MVC’s DispatcherServlet, they have access to the matched handler and the ModelAndView, which servlet filters do not. This page covers the lifecycle, registration, common use cases, and when to choose an interceptor over a filter.

The interceptor lifecycle

HandlerInterceptor defines three callbacks, all with default no-op implementations so you override only what you need.

import jakarta.servlet.http.*;
import org.springframework.web.servlet.*;

public class TimingInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
        req.setAttribute("startTime", System.currentTimeMillis());
        return true;  // false short-circuits: the handler is NOT invoked
    }

    @Override
    public void postHandle(HttpServletRequest req, HttpServletResponse res,
                           Object handler, ModelAndView mav) {
        // runs after the handler, before the view renders
    }

    @Override
    public void afterCompletion(HttpServletRequest req, HttpServletResponse res,
                                Object handler, Exception ex) {
        long start = (long) req.getAttribute("startTime");
        long took = System.currentTimeMillis() - start;
        System.out.printf("%s %s -> %d (%d ms)%n",
                req.getMethod(), req.getRequestURI(), res.getStatus(), took);
    }
}
CallbackWhen it runsCan stop the request?
preHandleBefore the handlerYes — return false
postHandleAfter handler, before view renderNo
afterCompletionAfter the response is completeNo

Note: Returning false from preHandle stops the chain — you are then responsible for writing the response (e.g. setting a 401 status). For @RestController apps postHandle is rarely useful because there is no view to render.

Registering an interceptor

Register interceptors in a WebMvcConfigurer. You can scope them to path patterns and exclude others.

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    private final AuthInterceptor authInterceptor;

    public WebConfig(AuthInterceptor authInterceptor) {
        this.authInterceptor = authInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new TimingInterceptor());

        registry.addInterceptor(authInterceptor)
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/public/**", "/api/auth/login");
    }
}

Because AuthInterceptor is a Spring bean, it can use constructor injection like any other component.

Use case: API key authentication

import jakarta.servlet.http.*;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class AuthInterceptor implements HandlerInterceptor {

    private final ApiKeyService apiKeys;

    public AuthInterceptor(ApiKeyService apiKeys) {
        this.apiKeys = apiKeys;
    }

    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
        String key = req.getHeader("X-API-Key");
        if (key == null || !apiKeys.isValid(key)) {
            res.setStatus(HttpStatus.UNAUTHORIZED.value());
            return false;   // stop here; handler is never reached
        }
        return true;
    }
}

Request:

curl -i http://localhost:8080/api/orders   # no X-API-Key header

Output:

HTTP/1.1 401 Unauthorized

Tip: For real authentication and authorization, prefer Spring Security filters over a hand-rolled interceptor. Interceptors are great for lightweight, app-specific concerns like timing, request logging, or audit tagging.

Common use cases

  • Timing / metrics — measure handler duration in preHandle / afterCompletion.
  • Request logging — log method, path, status, and elapsed time.
  • Lightweight auth — reject requests missing an API key.
  • Correlation IDs — attach a tracing id to MDC for the request’s lifetime.
  • Locale / tenant resolution — set per-request context.

Interceptor vs filter

Both wrap request handling, but at different layers. A servlet filter runs in the servlet container, outside Spring MVC; an interceptor runs inside the DispatcherServlet.

AspectInterceptorFilter
LayerSpring MVC dispatcherServlet container
Knows the handler?Yes (Object handler)No
Access to ModelAndViewYesNo
Can modify raw streamNo (response already routed)Yes (wrap request/response)
Applies to non-MVC requestsNoYes (all servlets/resources)
RegistrationWebMvcConfigurer@Component / FilterRegistrationBean

Choose an interceptor when you need handler-aware, MVC-scoped logic. Choose a filter when you must touch the raw request/response stream or affect every request, including static resources.

Pitfalls

  • postHandle does not run if preHandle returned false or the handler threw — use afterCompletion for guaranteed cleanup.
  • Interceptors do not see requests that never reach the dispatcher (e.g. blocked earlier by a filter).
  • Throwing from preHandle propagates to your exception handlers; setting a status and returning false does not.
Last updated June 13, 2026
Was this helpful?