What is AuthorizationDeniedException in Spring Security 6, and how does it relate to AccessDeniedException and the AccessDeniedHandler?
answer
- AuthorizationDeniedException extends AccessDeniedException
- AuthorizationManager-based, SS6 unified model
- carries AuthorizationResult
- @HandleAuthorizationDenied post-processing
- filter path → handler; MVC path → @ExceptionHandler
basics
~10 sAuthorizationDeniedException is the exception the modern AuthorizationManager-based checks (like @PreAuthorize and AuthorizationFilter) throw when access is denied. It extends AccessDeniedException, so it flows through the same ExceptionTranslationFilter and AccessDeniedHandler to a 403.
solid answer
~40 sIn Spring Security 6 the authorization model is unified around AuthorizationManager. When a check denies access it throws AuthorizationDeniedException, which is a subclass of AccessDeniedException and carries the AuthorizationResult/decision. Because it IS an AccessDeniedException, everything built for AccessDeniedException still applies: if it propagates to the ExceptionTranslationFilter in the filter chain (e.g. from AuthorizationFilter enforcing authorizeHttpRequests), your AccessDeniedHandler turns it into a 403. For method security, @PreAuthorize failures also throw AuthorizationDeniedException, but thrown inside the controller invocation they're typically resolved by Spring MVC's exception handling rather than the filter-chain handler. The subclass exists so denials carry richer context and can be post-processed (e.g. @PreAuthorize with a fallback via @HandleAuthorizationDenied).
code
java · 21 lines// Catch the base type so BOTH the legacy AccessDeniedException
// and the SS6 AuthorizationDeniedException subclass are handled.
@RestControllerAdvice
class SecurityExceptionAdvice {
@ExceptionHandler(AccessDeniedException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
Map<String, Object> onDenied(AccessDeniedException ex,
HttpServletRequest req) {
return Map.of(
"status", 403,
"error", "forbidden",
"path", req.getRequestURI());
}
}
// This @PreAuthorize failure throws AuthorizationDeniedException
// (a subclass of AccessDeniedException) inside the MVC dispatch.
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin/report")
String report() { return "secret"; }go deeper
Just know it's the exception that means 'authenticated but denied' and leads to 403.
Know it extends AccessDeniedException and therefore reuses the same handler wiring.
Explain the AuthorizationManager unification, the two propagation paths, and why you may need both a handler and an @ExceptionHandler.
Discuss @HandleAuthorizationDenied post-processing, typed AuthorizationResult, and designing consistent 403 behavior across filter and method security without duplicated envelopes.
**Background.** Spring Security 6 replaced the older voter/`AccessDecisionManager` machinery with `AuthorizationManager<T>` as the single authorization abstraction. Both URL security (`AuthorizationFilter` backing `authorizeHttpRequests`) and method security (`@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, `@PostFilter`) now go through `AuthorizationManager`. **The exception.** When an `AuthorizationManager` returns a denied decision, the enforcing code throws `org.springframework.security.authorization.AuthorizationDeniedException`. Crucially, **it extends `AccessDeniedException`** (from `org.springframework.security.access`). So it is a drop-in for everything that already handles `AccessDeniedException`: - The `ExceptionTranslationFilter` catches `AccessDeniedException` (and therefore its subclass) and routes to the `AccessDeniedHandler` (403) or `AuthenticationEntryPoint` (401 for anonymous). - A `@ExceptionHandler(AccessDeniedException.class)` in a `@ControllerAdvice` also matches it, because subclasses are matched. **What the subclass adds.** `AuthorizationDeniedException` carries the `AuthorizationResult` (the decision detail). This enables richer handling — for example, Spring Security 6.3+ can *post-process* a denied `@PreAuthorize`/`@PostAuthorize` result via a `@HandleAuthorizationDenied` deny handler that returns a masked/fallback value instead of throwing. That's only possible because the denial is a first-class typed result, not just a generic exception. **Two propagation paths (the senior distinction).** 1. **Filter-chain authorization** (`authorizeHttpRequests`): `AuthorizationFilter` runs *before* the `DispatcherServlet`. Its `AuthorizationDeniedException` propagates up to `ExceptionTranslationFilter` → your **`AccessDeniedHandler`** produces the 403. 2. **Method security** (`@PreAuthorize` on a controller/service): the exception is thrown *during* the controller method invocation, i.e. inside the `DispatcherServlet`. Spring MVC's `HandlerExceptionResolver` catches it first, so a **`@ExceptionHandler`/`@ControllerAdvice`** handles it and, unless it rethrows past the filter, the `AccessDeniedHandler` never runs. (Spring Boot's default `ResponseStatusExceptionResolver` won't map it automatically because `AccessDeniedException` has no `@ResponseStatus`; without an advice you can get a 500. Spring Security's `AuthorizationManagerBeforeMethodInterceptor` and Boot error handling usually still surface 403, but for a *custom* body you need the advice.) **Practical implication.** To return a uniform 403 envelope regardless of where the denial originates, wire **both**: the `AccessDeniedHandler` (filter path) and a `@ControllerAdvice @ExceptionHandler(AccessDeniedException.class)` returning `HttpStatus.FORBIDDEN` (method-security path). Both catch `AuthorizationDeniedException` via the superclass. **Gotcha.** Don't try to catch `AuthorizationDeniedException` specifically and forget the base type — catch/handle `AccessDeniedException` so you cover legacy throwers and the new subclass together.
- If AuthorizationDeniedException extends AccessDeniedException, why does the class even exist?To carry the AuthorizationResult/decision so denials are typed and richer than a generic exception — enabling features like @HandleAuthorizationDenied post-processing that returns a masked or fallback value instead of throwing, and better diagnostics. It stays a subclass so existing 403 handling keeps working.
- For a @PreAuthorize denial on a controller method, does your filter-chain AccessDeniedHandler run?Usually not — the exception is thrown inside the DispatcherServlet during method invocation, so Spring MVC's HandlerExceptionResolver / @ExceptionHandler catches it first. The filter-chain AccessDeniedHandler only handles exceptions raised in the filter chain (e.g. authorizeHttpRequests via AuthorizationFilter).
saying these in an interview costs you the question
- Claiming AuthorizationDeniedException is unrelated to AccessDeniedHandler
- Thinking it does NOT extend AccessDeniedException
- Assuming the filter-chain handler always catches @PreAuthorize denials
- Saying SS6 still uses AccessDecisionManager/voters by default