Error Handling — @ControllerAdvice & ProblemDetail
Strukturiertes Error-Handling
Gute APIs geben strukturierte Fehler zurück — nicht Stacktraces.
Lektion 26: @ExceptionHandler
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ProductNotFoundException.class)
public ResponseEntity<ProblemDetail> handleNotFound(ProductNotFoundException ex) {
var problem = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
problem.setProperty("productId", ex.getProductId());
return ResponseEntity.status(404).body(problem);
}
}
Lektion 27: @ControllerAdvice
@RestControllerAdvice = @ControllerAdvice + @ResponseBody. Es fängt
Exceptions aus allen Controllern ab — ein zentraler Ort für Error-Handling.
Lektion 28: ProblemDetail (RFC 7807)
Spring Boot 3 liefert standardmäßig RFC 7807 Problem-Details:
{
"type": "https://example.com/errors/not-found",
"title": "Not Found",
"status": 404,
"detail": "Product 42 not found",
"instance": "/api/products/42"
}
RFC 7807 — Standardisiertes Fehlerformat
RFC 7807 definiert application/problem+json als Media-Type für Fehler.
Vorteile: maschinenlesbar, standardisiert, erweiterbar. Spring Boot 3
unterstützt es out of the box über ProblemDetail.
Lektion 29: Custom Exceptions
public class ProductNotFoundException extends RuntimeException {
private final Long productId;
public ProductNotFoundException(Long id) {
super("Product " + id + " not found");
this.productId = id;
}
public Long getProductId() { return productId; }
}
Lektion 30: Validation Errors & Global Format
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ProblemDetail> handleValidation(MethodArgumentNotValidException ex) {
var problem = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "Validation failed");
problem.setProperty("errors", ex.getBindingResult().getFieldErrors().stream()
.map(fe -> Map.of("field", fe.getField(), "message", fe.getDefaultMessage()))
.toList());
return ResponseEntity.badRequest().body(problem);
}
Exception-Hierarchie gezielt entwerfen
Zwei Basisklassen reichen: ResourceNotFoundException (404) und
BusinessException (400/409). Jede Exception trägt Kontext-Daten
(z. B. die fehlende ID). Der @RestControllerAdvice mappt Typen auf
HTTP-Status — Services werfen nur Fachausnahmen, kein ResponseEntity.
Praxis-Szenario: CropGuard: GlobalExceptionHandler
Ein Farmer ruft PUT /api/claims/99/assess auf — der Claim existiert nicht. ClaimService wirft ResourceNotFoundException(99L). Der GlobalExceptionHandler baut ein ProblemDetail mit Status 404.
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren: