Startseite » Blog DE » Spring Boot Exception Handling inkl. Beispiele

Spring Boot Exception Handling inkl. Beispiele

Fehler passieren. In jeder Anwendung. Egal, wie gut sie entwickelt wurde. Doch wie man mit Fehlern umgeht, entscheidet, ob die Anwendung stabil und sicher bleibt – oder zu einem Albtraum wird. In diesem Artikel schauen wir uns an, wie du Exception Handling in Spring Boot richtig umsetzt, um die Benutzererfahrung zu verbessern, Debugging zu erleichtern und vor allem Sicherheitsrisiken zu minimieren. 

Warum ist Exception Handling in Spring Boot so wichtig?

Spring Boot bietet von Haus aus einige Mechanismen für das Exception Handling, aber diese reichen oft nicht aus. Fehlendes oder schlechtes Exception Handling führt zu: 

  • Sicherheitslücken (z. B. durch Leaken von Stacktraces oder sensiblen Daten) 
  • Schlechter User Experience (z. B. durch kryptische Fehlermeldungen) 
  • Wartungsproblemen (weil nicht klar ist, wo der Fehler herkommt) 

Also: Wir brauchen eine klare Strategie, um Exceptions sauber abzufangen, zu loggen und angemessen zu verarbeiten. Lass uns das Schritt für Schritt durchgehen. 

Spring Boot Checked and Unchecked Exceptions

Unterschiede zwischen Checked und Unchecked Exceptions

In Spring Boot wird zwischen Checked Exceptions und Unchecked Exceptions unterschieden, um Fehler in der Anwendung zu behandeln:

  • Checked Exceptions sind von Exception abgeleitet (aber nicht von RuntimeException) und müssen explizit behandelt oder weitergereicht werden. Sie eignen sich für erwartbare Fehlerfälle, die vom Aufrufer gehandhabt werden sollten, z. B. fehlende Dateien oder Verbindungsprobleme (IOException, SQLException).

  • Unchecked Exceptions sind von RuntimeException abgeleitet und müssen nicht explizit behandelt werden. Sie werden oft für Programmierfehler verwendet, z. B. Null-Referenzen oder ungültige Zustände (NullPointerException, IllegalArgumentException).

In Spring Boot werden Unchecked Exceptions bevorzugt, da sie die Lesbarkeit des Codes verbessern, indem sie nicht zwingend mit try-catch behandelt werden müssen. Stattdessen kann Spring Boot mit @ControllerAdvice und @ExceptionHandler globale Exception-Handling-Mechanismen bereitstellen. Beispielsweise kann eine zentrale Fehlerbehandlung alle ResourceNotFoundException-Fehler in ein HTTP 404-Response umwandeln.

1. Unchecked Exception Beispiel (Empfohlen in Spring Boot)

Angenommen, wir haben einen Service, der eine Ressource anhand einer ID sucht. Falls die Ressource nicht existiert, werfen wir eine Unchecked Exception (RuntimeException).

Exception-Klasse (Unchecked)

public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

Service-Klasse

@Service
public class UserService {
    private final Map<Long, String> users = Map.of(1L, "Alice", 2L, "Bob");

    public String getUserById(Long id) {
        return users.getOrDefault(id, null);
    }
}

Controller

@RestController
@RequestMapping("/users")
public class UserController {
    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/{id}")
    public ResponseEntity<String> getUser(@PathVariable Long id) {
        String user = userService.getUserById(id);
        if (user == null) {
            throw new ResourceNotFoundException("User with ID " + id + " not found");
        }
        return ResponseEntity.ok(user);
    }
}

Globale Exception-Handling-Klasse

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<String> handleResourceNotFound(ResourceNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
    }
}

Ergebnis: Wenn der Benutzer mit der angegebenen ID nicht existiert, gibt die API eine HTTP 404 – Not Found-Antwort mit einer klaren Fehlermeldung zurück.

2. Checked Exception Beispiel

Angenommen, wir greifen auf eine externe Datenbank zu und müssen eine mögliche SQLException behandeln.

Service-Klasse mit Checked Exception

@Service
public class DatabaseService {
    public String fetchData() throws SQLException {
        throw new SQLException("Database connection failed!");
    }
}

Controller mit Checked Exception

@RestController
@RequestMapping("/data")
public class DataController {
    private final DatabaseService databaseService;

    public DataController(DatabaseService databaseService) {
        this.databaseService = databaseService;
    }

    @GetMapping
    public ResponseEntity<String> getData() {
        try {
            return ResponseEntity.ok(databaseService.fetchData());
        } catch (SQLException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error fetching data: " + e.getMessage());
        }
    }
}

Problem: Der Controller muss try-catch verwenden, um die SQLException zu behandeln, was den Code unübersichtlicher macht.

Die Basics: Exceptions in Spring Boot abfangen

Spring Boot bringt einen globalen Exception-Handler mit, der Fehler standardmäßig behandelt, indem er eine generische Fehlerseite oder eine JSON-Antwort zurückgibt. Das reicht für einfache Anwendungen, aber in der Praxis braucht es mehr Kontrolle. 

Globale Fehlerbehandlung mit @ControllerAdvice

Mit @ControllerAdvice können wir zentral alle Exceptions unserer Anwendung abfangen und eine einheitliche Antwort liefern. Beispiel:

@RestControllerAdvice 
public class GlobalExceptionHandler { 
 
    @ExceptionHandler(ResourceNotFoundException.class) 
    public ResponseEntity<String> handleResourceNotFound(ResourceNotFoundException ex) { 
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage()); 
    } 
 
    @ExceptionHandler(Exception.class) 
    public ResponseEntity<String> handleGenericException(Exception ex) { 
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Ein unerwarteter Fehler ist aufgetreten"); 
    } 
}

Hier fangen wir gezielt ResourceNotFoundException ab und liefern einen 404-Statuscode zurück. Alle anderen Fehler landen in einem generischen Handler. 

Spezifische Fehlerseiten für Web-Anwendungen

Für klassische Spring-MVC-Webanwendungen kann eine benutzerfreundliche Fehlerseite definiert werden: 

  • Erstelle eine Datei error.html unter src/main/resources/templates. 
  • Spring Boot zeigt diese Seite automatisch bei Fehlern an. 

Falls du unterschiedliche Fehlerseiten für verschiedene Statuscodes möchtest, kannst du unter src/main/resources/templates/error/ HTML-Dateien wie 404.html oder 500.html ablegen. 

Advanced Exception Handling Spring Boot

Fortgeschrittenes Exception Handling mit @ResponseStatus und ErrorResponse

Wenn du Exceptions mit HTTP-Statuscodes verknüpfen willst, kannst du @ResponseStatus nutzen: 

@ResponseStatus(HttpStatus.NOT_FOUND) 
public class ResourceNotFoundException extends RuntimeException { 
    public ResourceNotFoundException(String message) { 
        super(message); 
    } 
} 

Oder noch besser: Ein konsistentes Fehlerformat mit einer eigenen ErrorResponse-Klasse. 

Tipp: Wenn du eine exakte Zurückverfolgung eines Fehlers ermöglichen möchtest, dann kannst du eigene eindeutige Fehlercodes hinzufügen. Dies ermöglicht es dir den genau Ursprung zu ermitteln. Die Schwierigkeit dabei ist es , die Fehlercodes eindeutig zu setzen. Hierzu kannst du einen zentralen Enum verwenden und dort auch bei Anpassungen erweitern. Ich würde empfehlen mit einer 6-8 Stelligen Zahl anzufangen. Sowas wie “1000000”. Die erste Zahl kann je nach deiner Logik für einen anderen Microservice oder Controller stehen. 

public class ErrorResponse { 
    private String message; 
    private int status; 
    private LocalDateTime timestamp; 
     
    public ErrorResponse(String message, int status) { 
        this.message = message; 
        this.status = status; 
        this.timestamp = LocalDateTime.now(); 
    } 
     
    // Getter & Setter 
} 

Und im @ControllerAdvice: 

@ExceptionHandler(ResourceNotFoundException.class) 
public ResponseEntity<ErrorResponse> handleResourceNotFound(ResourceNotFoundException ex) { 
    ErrorResponse errorResponse = new ErrorResponse(ex.getMessage(), HttpStatus.NOT_FOUND.value()); 
    return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); 
} 

Damit bekommst du eine saubere JSON-Antwort bei Fehlern: 

{ 
  "message": "Resource not found", 
  "status": 404, 
  "timestamp": "2025-02-18T10:15:30" 
} 
Custom Exceptions Spring Boot

Custom Exception Klassen mit zusätzlichen Feldern

Oft benötigt man Fehlerantworten mit mehr Kontext. Eine eigene Exception mit zusätzlichen Feldern kann helfen: 

public class BusinessException extends RuntimeException { 
    private final int errorCode; 
     
    public BusinessException(String message, int errorCode) { 
        super(message); 
        this.errorCode = errorCode; 
    } 
     
    public int getErrorCode() { 
        return errorCode; 
    } 
} 

Und im Exception Handler:

@ExceptionHandler(BusinessException.class) 
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) { 
    ErrorResponse errorResponse = new ErrorResponse(ex.getMessage(), ex.getErrorCode()); 
    return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); 
} 

Umgang mit mehreren @ControllerAdvice Dateien

In größeren Anwendungen kann es sinnvoll sein, mehrere @ControllerAdvice-Klassen zu verwenden. Zum Beispiel für verschiedene Module: 

@RestControllerAdvice(basePackages = "com.example.user") 
public class UserExceptionHandler { 
    @ExceptionHandler(UserNotFoundException.class) 
    public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex) { 
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Benutzer nicht gefunden"); 
    } 
}
@RestControllerAdvice(basePackages = "com.example.order") 
public class OrderExceptionHandler { 
    @ExceptionHandler(OrderNotFoundException.class) 
    public ResponseEntity<String> handleOrderNotFound(OrderNotFoundException ex) { 
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Bestellung nicht gefunden"); 
    } 
}

Priorität der @ControllerAdvice Klassen setzen

Spring Boot verarbeitet @ControllerAdvice-Klassen in einer bestimmten Reihenfolge. Falls es mehrere gibt, kann die Priorität mit @Order festgelegt werden:

@RestControllerAdvice 
@Order(1) 
public class HighPriorityExceptionHandler { 
    @ExceptionHandler(RuntimeException.class) 
    public ResponseEntity<String> handleRuntime(RuntimeException ex) { 
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Hohe Priorität Fehlerbehandlung"); 
    } 
}
@RestControllerAdvice 
@Order(2) 
public class DefaultExceptionHandler { 
    @ExceptionHandler(Exception.class) 
    public ResponseEntity<String> handleGeneric(Exception ex) { 
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Standard Fehlerbehandlung"); 
    } 
}

@Order(1) sorgt dafür, dass diese Exception-Handler vor anderen ausgeführt wird.

Spring Boot Exception handling security

Sicherheitsaspekte beim Exception Handling

Schlecht umgesetztes Exception Handling kann ein Sicherheitsrisiko darstellen. Hier sind einige häufige Fehler und wie du sie vermeidest: 

Stacktraces in API-Responses leaken 

  • Niemals Exceptions einfach mit ex.printStackTrace() in die Response schreiben! 
  • Nutze stattdessen Logger: 
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); 
 
@ExceptionHandler(Exception.class) 
public ResponseEntity<String> handleGenericException(Exception ex) { 
    logger.error("Interner Fehler: ", ex); 
    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Ein Fehler ist aufgetreten"); 
} 

Performance-Optimierung beim Exception Handling

Exceptions in Java sind teuer, weil sie Stacktraces generieren. Hier sind Best Practices, um die Performance zu verbessern: 

1. Exceptions nicht für Kontrollfluss nutzen 

  • Schlechte Praxis: try-catch für Logik verwenden. 
  • Besser: Vorher prüfen mit if oder Optional. 

2. Unchecked Exceptions für Geschäftslogik 

  • Checked Exceptions nur für externe API-Aufrufe oder I/O-Operationen nutzen. 

3. Stacktraces nur bei echten Fehlern loggen 

  • Ausnahme: Bei erwartbaren Fehlern reicht eine knappe Logging-Nachricht. 
bug that knows what to do

Handling von Validierungsfehlern mit @Valid und @ExceptionHandler

Spring Boot unterstützt automatische Validierung mit @Valid:

public class User { 
    @NotNull 
    private String name; 
 
    @Email 
    private String email; 
     
    // Getter & Setter 
} 

Im Controller: 

@PostMapping("/users") 
public ResponseEntity<String> createUser(@Valid @RequestBody User user) { 
    return ResponseEntity.ok("User gespeichert"); 
} 

Aber wenn die Validierung fehlschlägt? Hier kommt @ExceptionHandler ins Spiel: 

@ExceptionHandler(MethodArgumentNotValidException.class) 
public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) { 
    Map<String, String> errors = new HashMap<>(); 
    ex.getBindingResult().getFieldErrors().forEach(error -> errors.put(error.getField(), error.getDefaultMessage())); 
    return ResponseEntity.badRequest().body(errors); 
} 

Dadurch erhalten wir eine strukturierte Fehlermeldung: 

{ 
  "name": "Name darf nicht leer sein", 
  "email": "Muss eine gültige E-Mail sein" 
} 

Fazit

Exception Handling in Spring Boot ist nicht nur eine Frage der Sauberkeit, sondern auch der Sicherheit und Performance. Mit diesen Techniken hast du ein stabiles, sicheres und professionelles Exception Handling in deiner Spring Boot Anwendung. 🚀 

Nach oben scrollen
WordPress Cookie Plugin von Real Cookie Banner