Exception handling options, @ControllerAdvice and @ExceptionHandler, how exceptions are matched, securing MVC apps with Spring Security 6, method-level security, and dependency injection in controllers.
Published September 25, 2026
Two themes run through this lesson. The first is centralised error handling, which interviewers expect you to have implemented. The second is Spring Security 6. Many prepared answers still describe WebSecurityConfigurerAdapter, which no longer exists. Give the current API.
Short answer: There are four levels:
@ExceptionHandler methods inside a controller, which handle that controller's exceptions only.@ControllerAdvice / @RestControllerAdvice: global handlers shared by all controllers.@ResponseStatus on custom exception classes, to map an exception to a status code.HandlerExceptionResolver implementations, for full low-level control.Spring Boot adds a fallback /error endpoint, BasicErrorController, for anything that isn't handled.
Key points to cover:
ResponseStatusException lets you throw a status code directly: throw new ResponseStatusException(HttpStatus.NOT_FOUND, "order 42 not found").Learn it in depth → Spring Exception Handling
@ControllerAdvice?Short answer: Create a class annotated with @RestControllerAdvice (or @ControllerAdvice for view-based apps), and add @ExceptionHandler methods. It applies to all controllers, unless you narrow it (basePackages, annotations, assignableTypes).
@RestControllerAdvice
class GlobalExceptionHandler extends ResponseEntityExceptionHandler { // also handles Spring's built-in MVC exceptions
@ExceptionHandler(ResourceNotFoundException.class)
ProblemDetail handleNotFound(ResourceNotFoundException ex) {
return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
}
@ExceptionHandler(Exception.class)
ProblemDetail handleUnexpected(Exception ex) {
log.error("Unhandled error", ex); // full details in the logs only
return ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, "Something went wrong");
}
}
Key points to cover:
spring.mvc.problemdetails.enabled=true, so that Spring's own exceptions also use the RFC 9457 format.@ExceptionHandler used for?Short answer: It marks a method that handles specific exception types thrown from handler methods, and builds the error response: status, body and headers. Its parameters can include the exception, the request and the Locale. It can return a ProblemDetail, a ResponseEntity, a DTO, or a view (in MVC apps).
Short answer: It matches the thrown exception against the types declared in the @ExceptionHandler annotations, choosing the closest match in the class hierarchy. A handler for ResourceNotFoundException beats a handler for RuntimeException, which beats one for Exception.
@ControllerAdvice ones.@Order decides.Short answer: Spring Security is the standard choice. It gives you:
authorizeHttpRequests) and at method level (@PreAuthorize).Also secure the rest of the stack:
Learn it in depth → Spring Security Overview
Short answer: Through a servlet filter chain. DelegatingFilterProxy passes each request to Spring Security's FilterChainProxy, which runs the ordered security filters (authentication, exception translation, authorisation) before the request reaches the DispatcherServlet. In Spring Security 6, you configure it by declaring a SecurityFilterChain bean:
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
SecurityFilterChain security(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login", "/css/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.build();
}
}
Common trap: describing extends WebSecurityConfigurerAdapter. It was deprecated in 5.7, and removed in Spring Security 6 (Spring Boot 3).
Key points to cover:
@AuthenticationPrincipal injects the current user into controller methods, and the security context is available to views.Short answer:
Key points to cover:
anyRequest().authenticated()), and test the security rules with spring-security-test (@WithMockUser).Short answer: Enable it with @EnableMethodSecurity (Spring Security 6), then annotate service or controller methods:
@PreAuthorize and @PostAuthorize take SpEL expressions.@Secured and @RolesAllowed are available when enabled.@Configuration
@EnableMethodSecurity // replaces @EnableGlobalMethodSecurity(prePostEnabled = true)
class MethodSecurityConfig { }
@Service
class InvoiceService {
@PreAuthorize("hasRole('ACCOUNTANT') or #customerId == authentication.principal.customerId")
public List<Invoice> invoicesFor(long customerId) { … }
}
Key points to cover:
@Transactional.Learn it in depth → Role-Based Access Control
Short answer: Controllers, services, repositories, interceptors and advice classes are all Spring beans. The container supplies each one's collaborators, instead of the classes creating them. Controllers stay thin, delegating to injected services, which delegate to injected repositories.
Learn it in depth → Spring Dependency Injection
Short answer: Controllers are discovered by component scanning (@Controller/@RestController), and created as singletons. Their dependencies are injected, preferably through a constructor. Separately, method-level injection of request data (@PathVariable, @RequestBody, Principal, Locale) is handled by argument resolvers on every request.
@RestController
class CartController {
private final CartService carts; // constructor-injected once
CartController(CartService carts) { this.carts = carts; }
@PostMapping("/cart/items")
CartDto add(@AuthenticationPrincipal UserDetails user, // resolved per request
@Valid @RequestBody AddItem req) {
return carts.add(user.getUsername(), req);
}
}
Short answer: Constructor, setter and field injection. Use constructor injection for required dependencies, and setter injection for optional ones. Avoid field injection in production code: it hides dependencies, prevents final fields, and makes tests depend on Spring or reflection.
Common trap: "field injection is good for optional dependencies". For optional dependencies, use ObjectProvider<T>, Optional<T>, or a setter.
Short answer:
@WebMvcTest.Q: What's the difference between @ControllerAdvice and @RestControllerAdvice?
A: @RestControllerAdvice = @ControllerAdvice + @ResponseBody, so handler return values are written as the response body (JSON). Use it for APIs.
Q: Which HTTP status should an authentication failure return, and which an authorisation failure? A: 401 Unauthorized means "who are you?": missing or invalid credentials. 403 Forbidden means "I know who you are, but you're not allowed".
Q: Does @ExceptionHandler catch exceptions thrown in filters?
A: No. Filters run before the DispatcherServlet. Handle those errors in the filter itself, or through Spring Security's AuthenticationEntryPoint and AccessDeniedHandler.
Q: How do you test that an endpoint is secured?
A: With @WebMvcTest plus spring-security-test. Call without authentication and expect 401, use @WithMockUser(roles = "USER") and expect 403 on admin paths, and use @WithMockUser(roles = "ADMIN") and expect 200.