Contents

@RestControllerAdvice combines @ControllerAdvice and @ResponseBody. Methods annotated with @ExceptionHandler are invoked whenever the specified exception escapes any controller method.

import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) { ErrorResponse body = new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage()); return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body); } @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleGeneric(Exception ex) { ErrorResponse body = new ErrorResponse( HttpStatus.INTERNAL_SERVER_ERROR.value(), "An unexpected error occurred"); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(body); } }
public class ResourceNotFoundException extends RuntimeException { private final String resourceType; private final Long id; public ResourceNotFoundException(String resourceType, Long id) { super(resourceType + " with id " + id + " was not found"); this.resourceType = resourceType; this.id = id; } public String getResourceType() { return resourceType; } public Long getId() { return id; } }

A consistent error body makes it easy for API consumers to parse errors programmatically.

import java.time.Instant; public class ErrorResponse { private final int status; private final String message; private final Instant timestamp; public ErrorResponse(int status, String message) { this.status = status; this.message = message; this.timestamp = Instant.now(); } public int getStatus() { return status; } public String getMessage() { return message; } public Instant getTimestamp() { return timestamp; } }

When a ResourceNotFoundException is thrown from any controller, the client receives:

{ "status": 404, "message": "Product with id 99 was not found", "timestamp": "2026-03-10T08:15:30Z" }

When a @RequestBody fails Bean Validation, Spring throws MethodArgumentNotValidException. Handle it to return readable field-level error messages.

import org.springframework.http.ResponseEntity; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import java.util.Map; import java.util.stream.Collectors; @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<Map<String, String>> handleValidation( MethodArgumentNotValidException ex) { Map<String, String> errors = ex.getBindingResult() .getFieldErrors() .stream() .collect(Collectors.toMap( FieldError::getField, fe -> fe.getDefaultMessage() == null ? "invalid" : fe.getDefaultMessage() )); return ResponseEntity.badRequest().body(errors); } }