Careeroza
@ControllerAdvice and @ExceptionHandler
A Global Handler
@RestControllerAdvice (@ControllerAdvice + @ResponseBody) is a bean whose @ExceptionHandler methods apply to all controllers.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public Map<String, Object> notFound(ResourceNotFoundException ex) {
return Map.of("status", 404, "message", ex.getMessage());
}
@ExceptionHandler({InsufficientStockException.class, DuplicateSkuException.class})
@ResponseStatus(HttpStatus.CONFLICT)
public Map<String, Object> conflict(RuntimeException ex) {
return Map.of("status", 409, "message", ex.getMessage());
}
}Example
curl -i localhost:8080/api/products/42Example output
HTTP/1.1 404 Not Found
Content-Type: application/json
{"status":404,"message":"Product 42 not found"}Which Handler Wins?
When several handlers could match, Spring picks the one for the closest exception type in the class hierarchy. A handler for Exception is therefore a safe "catch everything else" net. The plain-Java version of that lookup:
Example
import java.util.*;
public class Main {
static final Map<Class<?>, Integer> handlers = new LinkedHashMap<>();
static String handle(RuntimeException ex) {
Class<?> type = ex.getClass();
while (type != null) { // walk up: closest match first
Integer status = handlers.get(type);
if (status != null)
return status + " (handler for " + type.getSimpleName() + "): " + ex.getMessage();
type = type.getSuperclass();
}
return "500 (no handler)";
}
public static void main(String[] args) {
handlers.put(ResourceNotFoundException.class, 404);
handlers.put(BusinessException.class, 409);
handlers.put(Exception.class, 500);
System.out.println(handle(new ResourceNotFoundException("Product 42 not found")));
System.out.println(handle(new InsufficientStockException("Only 2 left of Wireless Mouse")));
System.out.println(handle(new BusinessException("Coupon expired")));
System.out.println(handle(new IllegalStateException("Payment gateway down")));
}
}
class BusinessException extends RuntimeException { BusinessException(String m) { super(m); } }
class ResourceNotFoundException extends BusinessException { ResourceNotFoundException(String m) { super(m); } }
class InsufficientStockException extends BusinessException { InsufficientStockException(String m) { super(m); } }Output
404 (handler for ResourceNotFoundException): Product 42 not found 409 (handler for BusinessException): Only 2 left of Wireless Mouse 409 (handler for BusinessException): Coupon expired 500 (handler for Exception): Payment gateway down
Local Handlers
An @ExceptionHandler method inside a controller only handles exceptions from that controller, and it wins over the global advice. Use it rarely; one global handler keeps the format consistent.
Tip: Limit an advice to some controllers with @RestControllerAdvice(basePackages = "com.shopease.product") or assignableTypes = ....