Build1 publisher2 min readPublished
A filter rejects a request before Spring picks the controller that would have handled it
Spring gives three places to run code around a request, and each one sees a different amount. A dev.to walkthrough puts filters outside the DispatcherServlet, interceptors inside it, and aspects around any bean method.
The Engineer · Build desk
What happened
- A dev.to walkthrough places servlet filters outermost on raw HTTP, handler interceptors inside the DispatcherServlet, and AOP aspects around any bean method, and argues the ordering decides what each can see.
- A Filter comes from the Servlet spec, not Spring, and wraps the request before Spring runs, seeing only the raw HttpServletRequest and HttpServletResponse.
- Inside a filter, chain.doFilter divides inbound from outbound work, and skipping the call ends the request there, which the post illustrates by rejecting a missing API key.
- Returning false from preHandle stops the request and the controller never runs; the post's example sets status 401 when the X-User header is missing.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- decision Auth that depends on an annotation on the controller method can only go in the interceptor, because only the interceptor holds the handler object; the same rule in a filter has to be reconstructed from the URL string.
- constraint Metrics or request logging registered as an interceptor will undercount traffic, since requests for static files and error paths never reach a handler and so never reach the interceptor.
- capability Because an aspect wraps bean methods and fires for non-web calls, audit or retry logic that also has to cover a scheduler or queue listener can live in one place instead of two.
A filter holds both the request and the response, so it can wrap or replace the response body [8]. The dev.to post puts compression, CORS headers and request-body caching in that layer for that reason [17]. Spring Security is itself built as one big filter [9].
Both web layers can stop a request, and they stop it at different points. Skip `chain.doFilter` and the request ends inside the filter, before any Spring code runs; the post's example is rejecting a missing API key [7]. Return `false` from `preHandle` and the request also ends, but the DispatcherServlet has already read the URL and chosen the handler [3][10][11]. The interceptor rejection has already done handler resolution by then; the filter rejection has not [1].
The interceptor gets more information for that extra work. `preHandle` receives the handler object, and the post notes you can read the target method's annotations off it [13]. A filter never knows which controller will run [5]. So per-route auth keyed on an annotation is interceptor work, and so is request logging that names the method [18]. Registration is by URL pattern: `registry.addInterceptor(new AuthInterceptor()).addPathPatterns("/api/**")` in the post's `WebConfig` [12].
Interceptors also get three hooks around the controller call [14], one of which lets you touch the model before the view renders [18]. Only code running inside the DispatcherServlet can reach the model there.
Aspects sit deeper again. An aspect wraps any bean method and fires for non-web calls too [15]. A concern that must also apply when a scheduler or a queue listener calls the same service is not a web concern at all, and the aspect is the only one of the three layers that sees that call. The post stops there on aspects.
The ordering generalises the way the post states it: moving inward, each layer sees more Spring detail and less raw HTTP [16]. It holds where a servlet container such as Tomcat or Jetty is handing Spring a raw `HttpServletRequest` [2], and it describes only the requests that reach the DispatcherServlet, which is the component that maps a URL to a controller method and turns the return value into a response [3].
What to watch
- Where a custom filter lands relative to Spring Security's own filter chain, since both occupy the same outermost layer.
- Whether the ordering in the post's diagram holds outside a servlet stack; it assumes Tomcat or Jetty hands Spring a raw HttpServletRequest.
- How much URL matching and handler selection a preHandle rejection actually pays for in a running app, which the post does not measure.