Files
spring-boot-demo/filter-interceptor-aop

filter-interceptor-aop

Companion module for Filter vs HandlerInterceptor vs AOP in Spring Boot 4: Which Runs When on ankurm.com. A servlet Filter, a Spring MVC HandlerInterceptor and an @Aspect all wrap a request; this module prints the order they really run in, on a real Tomcat and under MockMvc, and then breaks the assumptions around it (errors, 404, static files, async, a body read twice, Spring Security).

mvn test regenerates every transcript in output/ (19 tests). Each test writes its transcript and asserts the same facts, so a number quoted in the article is an assertion that fails the build when it stops being true.

Versions

Spring Boot 4.1.2
Spring Framework 7.0.10
Spring Security 7.1.2
Tomcat (embedded) 11.0.27
AspectJ weaver 1.9.25.1
JDK Eclipse Temurin 25.0.5 (LTS)

The AOP starter is spring-boot-starter-aspectj. spring-boot-starter-aop has no 4.x release (its last version is 3.5.16).

Quickstart

export JAVA_HOME=/path/to/jdk-25
mvn test                     # 19 tests, rewrites output/01..19
./scripts/run.sh             # app on :8080
curl localhost:8080/diag/filters          # Tomcat's real filter chain, in order
./scripts/run.sh security    # same app behind Spring Security 7 (HTTP Basic demo:demo)
./scripts/run-all.sh         # tests + the scripts behind output/20, 21 (Maven Central) and 22 (jars in ~/.m2)

What is where

Package Contents
trace Trace: every component writes one line here, in the order it really runs (delete before shipping)
filter RawFilter (plain Filter, @Order(5)), AccessLogFilter (OncePerRequestFilter through a FilterRegistrationBean, order 10), ApiKeyFilter (auth, 20), VisibilityFilter (25), BodyFilter (request/response wrappers, 30), FaultFilter (throws, 40)
interceptor TraceInterceptor (A and B), GateInterceptor (returns false), RoleInterceptor (reads @RequiresRole), TimingInterceptor, LateHeaderInterceptor, VisibilityInterceptor, WebConfig
aop ControllerAspect, ServiceAspect, VisibilityAspect
service GreetingService (self-invocation, private method, new Helper()), FixedGreetingService (the @Lazy self-reference fix)
web HelloController, ApiExceptionHandler, DiagController (/diag/filters), TracingDispatcherServlet
security SecurityDemoConfig, only with the security profile

Endpoints

Endpoint Shows
GET /api/hello/{name} the full happy-path order
GET /api/boom, /api/service-boom, /api/unhandled handled, handled-from-service and unhandled exceptions
POST /api/echo + X-Body-Mode: consume|cache|consume-wrapped|replay the request body read twice
GET /api/hello/{name} + X-Resp-Mode: copy|forget ContentCachingResponseWrapper and copyBodyToResponse()
GET /api/secure/data + X-Api-Key: demo-key a filter rejecting a request
GET /api/admin/report + X-Role: admin an interceptor reading an annotation
GET /api/gated/hello?mode=write|silent preHandle returning false
GET /api/async-callable, /api/async-deferred async dispatch
GET /api/late/body a header added in postHandle
GET /api/visibility/{id} what each layer can see
GET /api/twice/{name}, /api/private/{name}, /api/helper/{name}, /api/twice-fixed/{name} what an aspect does not see

Index of captured output

File Contents
01-versions-and-jars.txt versions, the AOP starter's pom, jars, Framework 7 API facts read by reflection
02-filter-chain-order.txt Tomcat's real filter chain and dispatcher types
03-happy-path.txt one request, every layer, in order
04-exceptions.txt handled, service, unhandled and filter exceptions
05-static-404-error-dispatch.txt static resource, 404, 405 and the ERROR dispatch
06-short-circuits.txt filter 401, interceptor 403, preHandle false
07-async.txt Callable and DeferredResult, with threads
08-request-body.txt the body read twice, five ways, and the response wrapper
09-visibility.txt what a filter, an interceptor and an aspect can see
10-timing.txt elapsed time per layer (indicative)
11-late-header.txt a header set after the response is committed
12-no-handler.txt spring.web.resources.add-mappings=false
13-handler-cast.txt the unchecked HandlerMethod cast
14-path-patterns.txt which addPathPatterns patterns match which URLs
15-aop-traps.txt self-invocation, private method, new, and the fix
16-security-order.txt Spring Security's chain among the servlet filters
17-security-double-registration.txt a filter bean that runs twice
18-security-double-registration-fixed.txt the same, fixed
19-mockmvc-vs-server.txt the same requests through MockMvc and Tomcat
20-central-versions.txt Maven Central metadata (script)
21-framework-6.2-vs-7.0.txt javap of the interceptor and path-matching API in 6.2.19 vs 7.0.10 (script)
22-boot-bytecode-facts.txt bytecode facts: default filter dispatcher types, the dispatcherServlet back-off, the Security filter order constant, TestDispatcherServlet (script)

Timing in 10-timing.txt varies between runs; everything else is identical on a rerun. Thread names in 07-async.txt are relabelled (tomcat-1, mvc-task-1) so the file is reproducible.