What does the default AccessDeniedHandlerImpl do, and how does accessDeniedPage() differ from providing your own AccessDeniedHandler?
answer
- AccessDeniedHandlerImpl = default
- no page → sendError(403)
- page set → forward + WebAttributes.ACCESS_DENIED_403 attr
- accessDeniedPage = sugar over setErrorPage
- custom handler = full control (APIs)
basics
~20 sThe default AccessDeniedHandlerImpl sends a 403, either as a blank server error or by forwarding to a configured error page. accessDeniedPage() is a shortcut that just sets that forward path; a custom AccessDeniedHandler lets you fully control the status, body, and headers.
solid answer
~40 sAccessDeniedHandlerImpl is Spring Security's default handler. If no error page is set, it calls response.sendError(403), which the servlet container renders as its standard 403 page. If you call http.exceptionHandling(e -> e.accessDeniedPage("/403")), the handler instead forwards (server-side) to that path with the AccessDeniedException exposed as a request attribute, still with a 403 status. That's convenient for server-rendered apps. For REST/JSON, or when you need custom headers or a specific body shape, you implement AccessDeniedHandler yourself and register it via accessDeniedHandler(...) — you then own the entire response. accessDeniedPage and accessDeniedHandler are mutually exclusive ways to configure the same slot; the custom handler is strictly more flexible.
code
java · 12 lines// Option A: server-rendered app — forward to a themed 403 view
http.exceptionHandling(e -> e.accessDeniedPage("/error/403"));
// Option B: REST API — full control over the 403 response
http.exceptionHandling(e -> e.accessDeniedHandler((req, res, ex) -> {
res.setStatus(HttpServletResponse.SC_FORBIDDEN);
res.setHeader("Cache-Control", "no-store");
res.setContentType(MediaType.APPLICATION_JSON_VALUE);
res.getWriter().write("{\"error\":\"forbidden\"}");
}));
// A and B configure the SAME slot — choose one, not both.go deeper
Know the default returns 403 and that you can point it at an error page.
Distinguish accessDeniedPage (forward to a view) from a custom AccessDeniedHandler (full control), and know they share one slot.
Explain the forward vs sendError behavior, the request attribute exposed, and Boot's /error interaction.
Decide when default/Boot error handling suffices versus an explicit handler, weighing testability and coupling to MVC error infrastructure.
**The default.** When you configure nothing, `org.springframework.security.web.access.AccessDeniedHandlerImpl` is used. Its behavior: - **No error page configured:** `response.sendError(HttpServletResponse.SC_FORBIDDEN, ...)` — the servlet container returns its default 403 page (in Spring Boot, this is routed to the `/error` mapping / `BasicErrorController`, which may render Whitelabel HTML or a JSON error depending on the request's `Accept` header). - **Error page configured** (via `setErrorPage(...)`): it sets status 403, exposes the exception under the request attribute `WebAttributes.ACCESS_DENIED_403` (`RequestDispatcher.forward`), and **forwards** the request to that path. Because it's a forward (not a redirect), the URL in the browser stays the same and no extra round trip occurs. **`accessDeniedPage(String)`.** This DSL method is sugar: internally it constructs an `AccessDeniedHandlerImpl`, calls `setErrorPage(page)`, and installs it. Use it for server-side-rendered apps where a friendly 403 view suffices. It does **not** let you change the status code or write a custom body directly — it forwards to a view that does that. **Custom `AccessDeniedHandler` via `accessDeniedHandler(...)`.** You implement the interface and take full control: set any status (normally 403), any content type, write JSON/text, add headers, log, emit metrics, etc. This is the choice for APIs. **Mutual exclusivity.** Both `accessDeniedPage` and `accessDeniedHandler` configure the same single slot on `ExceptionHandlingConfigurer`. Setting both is contradictory — the last one wins / configuration is ambiguous; pick one. In practice, if you need anything beyond a plain forward-to-view, use `accessDeniedHandler`. **Boot interaction gotcha.** With the default handler and `sendError`, Spring Boot's error handling takes over via `/error`. If you've customized `ErrorController` or error attributes, your 403 body comes from there — meaning you might get consistent errors *without* a custom `AccessDeniedHandler` at all, as long as the exception reaches `sendError`. But relying on that couples your security errors to MVC error handling and only works when the exception reaches the container; a dedicated `AccessDeniedHandler` is more explicit and testable. **When to use which.** - Server-rendered app, want a themed 403 page → `accessDeniedPage("/403")`. - REST/JSON API, custom envelope, headers, logging → custom `AccessDeniedHandler`. - Minimal app, default container/Boot 403 acceptable → configure nothing.
- Does accessDeniedPage() redirect or forward to the error page?It forwards (server-side RequestDispatcher.forward) with a 403 status and the AccessDeniedException stored under the WebAttributes.ACCESS_DENIED_403 request attribute — no client redirect, so the browser URL is unchanged.
saying these in an interview costs you the question
- Saying accessDeniedPage issues a client redirect (302)
- Thinking you can set both accessDeniedPage and accessDeniedHandler simultaneously
- Believing the default handler returns 401
- Assuming a custom handler is always required even for simple server-rendered 403 pages