W3docs

Java Checked vs. Unchecked Exceptions

Der Unterschied zwischen checked und unchecked (Runtime-)Exceptions in Java und wann man welche verwendet.

Java unterteilt seine Exceptions in zwei Gruppen, die der Compiler unterschiedlich behandelt. Checked Exceptions müssen abgefangen oder deklariert werden; der Compiler verweigert die Kompilierung von Code, der sie ignoriert. Unchecked Exceptions haben diese Anforderung nicht — der Compiler lässt sie stillschweigend propagieren, und nur die Laufzeitumgebung fängt sie ab. Diese Unterscheidung ist eine der markantesten und meistdiskutierten Entscheidungen in Java, und ihr Verständnis prägt, wie man robusten Code schreibt.

Was ist was

Alles unter Throwable fällt in einen von drei Bereichen:

  • Error — Katastrophe auf JVM-Ebene. Unchecked. Nicht abfangen.
  • RuntimeException (eine Unterklasse von Exception) und alle ihre Untertypen. Unchecked.
  • Andere Unterklassen von Exception. Checked.

Die Regel lautet: "RuntimeException und Error sowie ihre Untertypen sind unchecked; alles andere unter Throwable ist checked." Das ist eine Unterscheidung im Klassenbaum, kein Konfigurationsschalter — es gibt keine Möglichkeit, eine RuntimeException checked oder eine IOException unchecked zu machen.

Was "checked" im Code bedeutet

Wenn ein Methodenrumpf eine checked Exception wirft, muss die Methode eines von zwei Dingen tun, sonst lässt sich das Programm nicht kompilieren:

// Option 1: catch it
public void load() {
  try {
    String text = Files.readString(path);   // throws IOException
  } catch (IOException e) {
    // ...
  }
}

// Option 2: declare it
public void load() throws IOException {
  String text = Files.readString(path);
}

Der Compiler durchläuft jede Methode und prüft: Gibt es für jede checked Exception, die entweichen könnte, entweder ein umschließendes catch oder eine throws-Klausel? Wenn nicht, Fehler.

Unchecked Exceptions haben keine solche Anforderung. Man kann eine IllegalArgumentException aus jeder Methode werfen, ohne die Signatur zu ändern, und jeder Aufrufer kann sie abfangen oder nicht.

Das Design-Konzept

Die Motivation hinter checked Exceptions war: Manche Fehler sind erwartete Ergebnisse der Operation — eine Datei existiert möglicherweise nicht, ein Netzwerk könnte ausgefallen sein — und die Sprache sollte sicherstellen, dass Aufrufer darüber nachdenken. Unchecked Exceptions sind für Programmierfehler gedacht — Null-Dereferenzierungen, Index außerhalb der Grenzen, ungültige Argumente — wo die richtige Lösung darin besteht, den Code zu korrigieren, nicht ein try/catch hinzuzufügen.

Diese Unterscheidung klingt sauber. In der Praxis bricht sie auf:

  • Geschichteter Code vervielfacht Deklarationen. Eine einfache IOException aus einem Konfigurations-Loader bleibt nicht dort — jede Methode, die sie aufruft, muss sie behandeln oder deklarieren. Bis die Exception den Controller erreicht, listet die Signatur sechs unzusammenhängende checked Exceptions auf.
  • Lambdas vertragen sie schlecht. Stream.map(this::parseLine) lässt sich nicht kompilieren, wenn parseLine eine checked Exception wirft, weil Functions apply keine deklariert.
  • Wrapping ist allgegenwärtig. Ein häufiger Workaround besteht darin, eine checked Exception sofort abzufangen und als Runtime Exception weiterzuwerfen, was den ursprünglichen Zweck zunichte macht.

Moderner Java-Code verwendet weniger checked Exceptions als die Standardbibliothek — manchmal gar keine. Neue Frameworks wie Spring setzen aus diesem Grund fast ausschließlich auf Runtime Exceptions.

Wann welche verwenden

Eine vertretbare Faustregel:

  • Checked verwenden, wenn der Aufrufer eine realistische Wiederherstellungsstrategie hat, die spezifisch für den Fehler ist — z.B. erneuter Versuch bei einem Netzwerkfehler, Fallback bei einem Parse-Fehler. Zwinge sie, darüber nachzudenken.
  • Unchecked verwenden, wenn der Fehler ein Bug ist (IllegalArgumentException, NullPointerException, IllegalStateException) oder wenn kein Aufrufer vernünftigerweise reagieren könnte.

Im Zweifelsfall unchecked bevorzugen. Die Kosten, es falsch zu machen, sind geringer, und man kann später immer noch verschärfen.

Die Catch-Seite

Ob eine Exception checked ist, verändert, wie das catch aussieht:

// checked: caller must catch or declare
try {
  parser.parse(path);
} catch (IOException e) {
  // handle
}

// unchecked: caller may catch but doesn't have to
try {
  return numbers.get(idx);
} catch (IndexOutOfBoundsException e) {
  return -1;
}

Aus Sicht des catch gibt es keinen Verhaltensunterschied — beide Blöcke fangen das ab, was sie deklarieren. Die Unterscheidung ist nur an der Aufrufstelle relevant, die nicht abfängt: eine checked Exception erzwingt eine throws-Deklaration; eine unchecked propagiert stillschweigend.

Ein häufiger Fehler: Exception abfangen

Da Exception das übergeordnete Element sowohl checked als auch unchecked Typen ist (außer Error), passt catch (Exception e) auf fast alles. Das ist oft ein Code-Smell:

try { complexOperation(); }
catch (Exception e) { log("failed"); }    // hides bugs and real failures alike

Das Problem liegt nicht darin, dass abgefangen wird — sondern dass zu viel abgefangen wird. Eine NullPointerException hier deutet auf einen Bug hin; eine IOException deutet auf einen echten behebbaren Fehler hin; eine RuntimeException aus einer nicht kontrollierten Bibliothek könnte alles sein. Sie gleich zu behandeln bedeutet meistens, keine von ihnen gut zu behandeln. Es empfiehlt sich, nur die spezifischen Typen abzufangen, für die man einen Plan hat.

Ein durchgearbeitetes Beispiel

Ein kleines Programm mit zwei Methoden: Eine deklariert eine checked IOException, die andere wirft eine unchecked IllegalArgumentException. Der Compiler behandelt sie unterschiedlich — nur die checked Exception erzwingt eine Behandlung an der Aufrufstelle.

java— editable, runs on the server

Beachte drei Dinge. Entferne throws java.io.IOException aus maybeReadFile, und die Datei lässt sich nicht kompilieren. Entferne das umschließende try/catch vom ersten Aufruf, und die Datei lässt sich ebenfalls nicht kompilieren. Aber um das abschließende requirePositive(-7) gibt es kein try/catch — und das kompiliert problemlos, obwohl der Aufruf das Programm zum Absturz bringen wird. Das ist die asymmetrische Behandlung auf einen Blick.

Was als Nächstes kommt

Wir haben über Typen wie IOException, RuntimeException, Error gesprochen — aber wie sind sie eigentlich miteinander verbunden? Der Klassenbaum ist klein und es lohnt sich, ihn auswendig zu lernen. Weiter zur Java Exception-Hierarchie.

Übungen

Übung
Welche Aussage über checked Exceptions ist korrekt?
Welche Aussage über checked Exceptions ist korrekt?
Was this page helpful?