@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);
}
}