Spring Boot Cheatsheet
Exception Handling
Use this Spring Boot reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
@ControllerAdvice / @RestControllerAdvice
The recommended single-point exception handling for REST APIs.
@RestControllerAdvice // = @ControllerAdvice + @ResponseBody public class GlobalExceptionHandler { // Handle a specific custom exception @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<ApiError> handleNotFound(ResourceNotFoundException ex, HttpServletRequest request) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(new ApiError(404, "Not Found", ex.getMessage(), Instant.now(), request.getRequestURI())); } // Handle validation errors from @RequestBody + @Valid @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<ApiError> handleValidation(MethodArgumentNotValidException ex, HttpServletRequest request) { String message = ex.getBindingResult().getFieldErrors().stream() .map(e -> e.getField() + ": " + e.getDefaultMessage()) .collect(Collectors.joining(", ")); return ResponseEntity.badRequest() .body(new ApiError(400, "Validation Failed", message, Instant.now(), request.getRequestURI())); } // Handle constraint violations on @PathVariable / @RequestParam @ExceptionHandler(ConstraintViolationException.class) public ResponseEntity<ApiError> handleConstraint(ConstraintViolationException ex, HttpServletRequest request) { String message = ex.getConstraintViolations().stream() .map(v -> v.getPropertyPath() + ": " + v.getMessage()) .collect(Collectors.joining(", ")); return ResponseEntity.badRequest() .body(new ApiError(400, "Constraint Violation", message, Instant.now(), request.getRequestURI())); } // Catch-all — log and return 500 @ExceptionHandler(Exception.class) public ResponseEntity<ApiError> handleAll(Exception ex, HttpServletRequest request) { log.error("Unhandled exception", ex); return ResponseEntity.internalServerError() .body(new ApiError(500, "Internal Server Error", "An unexpected error occurred", Instant.now(), request.getRequestURI())); } }
Standard Error Response Record
public record ApiError( int status, String error, String message, Instant timestamp, String path ) {}
Custom Exception Classes
// Base application exception public class AppException extends RuntimeException { private final HttpStatus status; public AppException(String message, HttpStatus status) { super(message); this.status = status; } public AppException(String message, HttpStatus status, Throwable cause) { super(message, cause); this.status = status; } public HttpStatus getStatus() { return status; } } // Typed subclasses public class ResourceNotFoundException extends AppException { public ResourceNotFoundException(String resource, Object id) { super("%s with id '%s' not found".formatted(resource, id), HttpStatus.NOT_FOUND); } } public class ConflictException extends AppException { public ConflictException(String message) { super(message, HttpStatus.CONFLICT); } } public class UnauthorizedException extends AppException { public UnauthorizedException(String message) { super(message, HttpStatus.UNAUTHORIZED); } } public class ForbiddenException extends AppException { public ForbiddenException(String message) { super(message, HttpStatus.FORBIDDEN); } }
// In @ControllerAdvice — handle all AppExceptions with one handler @ExceptionHandler(AppException.class) public ResponseEntity<ApiError> handleApp(AppException ex, HttpServletRequest req) { return ResponseEntity.status(ex.getStatus()) .body(new ApiError(ex.getStatus().value(), ex.getStatus().getReasonPhrase(), ex.getMessage(), Instant.now(), req.getRequestURI())); }
@ResponseStatus on Exception Classes
@ResponseStatus(HttpStatus.NOT_FOUND) // applied when no @ExceptionHandler matches public class ItemNotFoundException extends RuntimeException { public ItemNotFoundException(Long id) { super("Item " + id + " not found"); } }
@ControllerAdvicehandlers take precedence over@ResponseStatus. Use@ResponseStatusonly for simple cases with no custom response body.
Built-in Exceptions to Handle
| Exception | HTTP Status | Trigger |
|---|---|---|
MethodArgumentNotValidException | 400 | @Valid fails on @RequestBody |
ConstraintViolationException | 400 | @Validated fails on params |
BindException | 400 | @ModelAttribute validation fails |
HttpMessageNotReadableException | 400 | Malformed JSON body |
MissingServletRequestParameterException | 400 | Required @RequestParam missing |
MethodArgumentTypeMismatchException | 400 | Type coercion failure (e.g., string→int) |
HttpRequestMethodNotSupportedException | 405 | Wrong HTTP method |
HttpMediaTypeNotSupportedException | 415 | Wrong Content-Type |
HttpMediaTypeNotAcceptableException | 406 | Accept header mismatch |
NoResourceFoundException | 404 | No handler found (Spring 6.2+) |
MaxUploadSizeExceededException | 413 | File upload exceeds limit |
AccessDeniedException | 403 | Spring Security access denied |
AuthenticationException | 401 | Spring Security auth failure |
@ControllerAdvice Scoping
// Limit advice to specific packages or controllers @ControllerAdvice(basePackages = "com.example.api") @ControllerAdvice(assignableTypes = {UserController.class, OrderController.class}) @ControllerAdvice(annotations = RestController.class)
ResponseEntityExceptionHandler
Extend this abstract class to override Spring's default handling of standard MVC exceptions:
@RestControllerAdvice public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { @Override protected ResponseEntity<Object> handleMethodArgumentNotValid( MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) { List<String> errors = ex.getBindingResult().getFieldErrors().stream() .map(e -> e.getField() + ": " + e.getDefaultMessage()) .toList(); ProblemDetail detail = ProblemDetail.forStatusAndDetail(status, "Validation failed: " + String.join(", ", errors)); return ResponseEntity.badRequest().body(detail); } }
RFC 7807 Problem Details (Spring 6+)
Spring Boot 3.x can return standard application/problem+json responses automatically.
spring.mvc.problemdetails.enabled=true// Manually build a ProblemDetail @ExceptionHandler(ResourceNotFoundException.class) public ProblemDetail handleNotFound(ResourceNotFoundException ex) { ProblemDetail detail = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage()); detail.setTitle("Resource Not Found"); detail.setProperty("entityType", ex.getEntityType()); return detail; }
Logging in Exception Handlers
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); @ExceptionHandler(Exception.class) public ResponseEntity<ApiError> handleAll(Exception ex, HttpServletRequest request) { // Log with stack trace only for unexpected errors log.error("Unhandled exception on {} {}", request.getMethod(), request.getRequestURI(), ex); ... } @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<ApiError> handleNotFound(ResourceNotFoundException ex) { log.debug("Resource not found: {}", ex.getMessage()); // debug — not an error ... }
Common Pitfalls
- Multiple
@ExceptionHandlerfor the same type — only one@ControllerAdvicecan claim a given exception type; duplicates throw an ambiguous mapping error at startup. @ExceptionHandlerin@Controllervs@ControllerAdvice— a handler inside a@Controlleronly catches exceptions from that controller; use@ControllerAdvicefor global handling.- Order of
@ControllerAdvicebeans — use@Orderor implementOrderedto control which advice applies first when multiple exist. - Spring Security exceptions before MVC —
AccessDeniedExceptionandAuthenticationExceptionare handled by the Security filter chain, not@ControllerAdvice; customize viaAuthenticationEntryPointandAccessDeniedHandler. - Returning the exception message in prod — sanitize messages in
@ExceptionHandlerto avoid leaking stack traces or internal details; use generic messages for 500s. @ResponseBodymissing — using@ControllerAdvice(not@RestControllerAdvice) without@ResponseBodyon the handler returns a view name, not JSON.