Where does ExceptionTranslationFilter sit relative to AuthorizationFilter, and why does its position matter?
answer
- ExceptionTranslationFilter BEFORE AuthorizationFilter
- try/catch only wraps downstream filters
- upstream filters handle own errors
- custom authz filter → addFilterAfter
- misorder → raw 500 not 403
basics
~20 sExceptionTranslationFilter is placed just before AuthorizationFilter. Because it only wraps the filters after it in a try/catch, the authorization filter's AccessDeniedException is thrown downstream and caught by it. If it came after, it couldn't catch that exception.
solid answer
~40 sIn Spring Security 6 the order near the end of the chain is `ExceptionTranslationFilter` then `AuthorizationFilter`. This ordering is deliberate: `ExceptionTranslationFilter` wraps `chain.doFilter(...)` in a try/catch, so it can only handle exceptions thrown by filters that run *after* it. `AuthorizationFilter` is the component that evaluates access rules and throws `AccessDeniedException`, so it must sit downstream to be caught. Likewise, if a downstream authentication mechanism throws `AuthenticationException`, the same wrapper catches it. Filters *before* `ExceptionTranslationFilter` (session management, the authentication filters) handle their own errors; their exceptions won't be translated here. Getting the order wrong — e.g. placing a custom authorization check before ExceptionTranslationFilter — means its exceptions escape untranslated and surface as raw servlet errors instead of a clean 401/403.
code
java · 6 lines// A custom authorization filter that throws AccessDeniedException must run
// AFTER ExceptionTranslationFilter so its exception gets translated to 403.
http.addFilterAfter(new TenantAuthorizationFilter(), ExceptionTranslationFilter.class);
// WRONG: placed before -> AccessDeniedException escapes untranslated -> 500
// http.addFilterBefore(new TenantAuthorizationFilter(), ExceptionTranslationFilter.class);go deeper
Know it comes just before the authorization filter so it can catch that filter's exception.
Explain the try/catch-only-wraps-downstream mechanic and which filters handle their own errors.
Reason about custom-filter placement (addFilterAfter) and the 500-instead-of-403 symptom of misordering.
Connect it to method-security exceptions propagating up the same thread and to designing custom security filters that fit the translation boundary.
## The relevant slice of the chain A `SecurityFilterChain` is an ordered list. Near the end (Spring Security 6): ``` ... -> AnonymousAuthenticationFilter -> ExceptionTranslationFilter -> AuthorizationFilter (the last security filter; throws AccessDeniedException) -> [your controller / dispatcher] ``` ## Why order is the whole point `ExceptionTranslationFilter` works by wrapping the *rest of the chain* in try/catch: ```java try { chain.doFilter(request, response); } catch (Exception ex) { ... } ``` A try/catch can only catch exceptions from code that runs **inside** it. Since `chain.doFilter(...)` invokes every filter *after* this one, `ExceptionTranslationFilter` can only translate exceptions thrown **downstream**. Therefore: - `AuthorizationFilter` must be placed **after** `ExceptionTranslationFilter` — and it is. Its `AccessDeniedException` bubbles back up into the try/catch. - Any component further down (including your controller, if a method-security check throws) that raises `AccessDeniedException`/`AuthenticationException` will also be caught here. ## What it does NOT catch Filters that run **before** it — `SecurityContextHolderFilter`, `UsernamePasswordAuthenticationFilter`, `AnonymousAuthenticationFilter`, session management — have already executed and returned by the time the try/catch is entered on the way *down*, or they handle their own failures. For example, a bad login form submission is dealt with by `UsernamePasswordAuthenticationFilter`'s own `AuthenticationFailureHandler`, not by ExceptionTranslationFilter. ## Consequences of getting it wrong If you register a custom filter that performs authorization and throws `AccessDeniedException`, you must place it **after** `ExceptionTranslationFilter` (e.g. `addFilterAfter(...)`), or its exception will not be translated and the user gets a raw `500` instead of a `403`/login prompt. This is a classic custom-filter ordering bug. ## Method security nuance `@PreAuthorize` on a controller/service method throws `AccessDeniedException` from a Spring AOP interceptor, which runs *after* the whole filter chain has descended into the dispatcher. That exception propagates back up through `ExceptionTranslationFilter`'s try/catch, so it too is translated — provided the throwing code path is within the request thread that passed through the filter. ## When to think about this You rarely reorder built-in filters, but you must understand this whenever you add a custom filter (`addFilterBefore`/`addFilterAfter`) that can raise security exceptions, or when debugging why a security failure shows up as a 500 rather than a clean status.
- If a custom filter that throws AccessDeniedException is added before ExceptionTranslationFilter, what does the client see?A raw servlet error (typically 500), because the exception is thrown upstream of the try/catch and never reaches the translation logic.
- Does ExceptionTranslationFilter translate an AccessDeniedException thrown by a @PreAuthorize method?Yes — the method-security AOP interceptor runs downstream during request dispatch, so the exception propagates back up into ExceptionTranslationFilter's try/catch on the same thread.
saying these in an interview costs you the question
- Placing a custom authorization filter before ExceptionTranslationFilter and expecting a clean 403
- Believing ExceptionTranslationFilter catches errors from upstream authentication filters
- Thinking filter order is arbitrary rather than semantically required by the try/catch scope