Zum Inhalt springen
Java Anfänger 20 min

Eigene Exceptions

Eigene Exception-Klassen in Java erstellen: Custom Checked und Unchecked Exceptions, Exception-Hierarchien und Best Practices.

Aktualisiert:

Eigene Exceptions erstellen

Die eingebauten Exceptions decken viele Faelle ab — aber manchmal brauchst du spezifischere Fehlermeldungen fuer deine Anwendung. Eine BankKontoLeerException ist viel aussagekraeftiger als eine generische RuntimeException. In diesem Kapitel lernst du, eigene Exceptions zu erstellen.

Warum eigene Exceptions?

// SCHLECHT: Generische Exception -- was genau ist schiefgelaufen?
throw new RuntimeException("Fehler bei der Bestellung");

// GUT: Spezifische eigene Exception
throw new BestellungNichtGefundenException("Bestellung #12345 nicht gefunden");
throw new KontostandZuNiedrigException(konto, betrag);
throw new UngueltigeEmailException("max@");

Eigene Exceptions bieten:

  • Klarere Fehlermeldungen — was genau ist passiert?
  • Gezielteres Fangen — verschiedene Fehler unterschiedlich behandeln
  • Zusaetzliche Informationen — Kontostand, Bestellnummer, etc.

Eigene Unchecked Exception

Erweitere RuntimeException fuer Exceptions, die nicht gefangen werden muessen:

public class UngueltigesAlterException extends RuntimeException {

    public UngueltigesAlterException(int alter) {
        super("Ungueltiges Alter: " + alter + " (muss zwischen 0 und 150 liegen)");
    }
}
public class Person {
    private String name;
    private int alter;

    public Person(String name, int alter) {
        if (alter < 0 || alter > 150) {
            throw new UngueltigesAlterException(alter);
        }
        this.name = name;
        this.alter = alter;
    }
}

// Verwendung
var person = new Person("Max", 25);       // OK
// var person2 = new Person("Anna", -5);  // UngueltigesAlterException!

Eigene Checked Exception

Erweitere Exception fuer Exceptions, die gefangen werden muessen:

public class DateiNichtLesbarException extends Exception {

    private final String dateipfad;

    public DateiNichtLesbarException(String dateipfad) {
        super("Datei nicht lesbar: " + dateipfad);
        this.dateipfad = dateipfad;
    }

    public DateiNichtLesbarException(String dateipfad, Throwable ursache) {
        super("Datei nicht lesbar: " + dateipfad, ursache);
        this.dateipfad = dateipfad;
    }

    public String getDateipfad() {
        return dateipfad;
    }
}
public class DateiLeser {
    public String leseDatei(String pfad) throws DateiNichtLesbarException {
        try {
            return Files.readString(Path.of(pfad));
        } catch (IOException e) {
            throw new DateiNichtLesbarException(pfad, e); // Ursache mitgeben!
        }
    }
}

// Verwendung: Muss gefangen werden!
var leser = new DateiLeser();
try {
    var inhalt = leser.leseDatei("config.txt");
} catch (DateiNichtLesbarException e) {
    System.out.println(e.getMessage());
    System.out.println("Pfad: " + e.getDateipfad());
}

Exception mit zusaetzlichen Daten

Eigene Exceptions koennen beliebige Zusatzinformationen speichern:

public class KontostandZuNiedrigException extends RuntimeException {
    private final double kontostand;
    private final double gewuenscht;

    public KontostandZuNiedrigException(double kontostand, double gewuenscht) {
        super("Kontostand zu niedrig: %.2f EUR vorhanden, %.2f EUR gewuenscht"
                .formatted(kontostand, gewuenscht));
        this.kontostand = kontostand;
        this.gewuenscht = gewuenscht;
    }

    public double getKontostand() { return kontostand; }
    public double getGewuenscht() { return gewuenscht; }
    public double getFehlbetrag() { return gewuenscht - kontostand; }
}
public class Bankkonto {
    private double kontostand;

    public void abheben(double betrag) {
        if (betrag > kontostand) {
            throw new KontostandZuNiedrigException(kontostand, betrag);
        }
        kontostand -= betrag;
    }
}

// Verwendung
try {
    konto.abheben(5000);
} catch (KontostandZuNiedrigException e) {
    System.out.println(e.getMessage());
    System.out.printf("Dir fehlen: %.2f EUR%n", e.getFehlbetrag());
}

Exception-Hierarchie erstellen

Fuer komplexere Anwendungen kannst du eine Hierarchie von Exceptions bauen:

// Basis-Exception fuer die gesamte Anwendung
public class ShopException extends RuntimeException {
    public ShopException(String message) {
        super(message);
    }
    public ShopException(String message, Throwable cause) {
        super(message, cause);
    }
}

// Spezifische Exceptions
public class ProduktNichtGefundenException extends ShopException {
    private final String produktId;

    public ProduktNichtGefundenException(String produktId) {
        super("Produkt nicht gefunden: " + produktId);
        this.produktId = produktId;
    }

    public String getProduktId() { return produktId; }
}

public class WarenkorbLeerException extends ShopException {
    public WarenkorbLeerException() {
        super("Der Warenkorb ist leer!");
    }
}

public class LagerBestandException extends ShopException {
    public LagerBestandException(String produkt, int verfuegbar, int gewuenscht) {
        super("Nicht genug '%s' auf Lager: %d verfuegbar, %d gewuenscht"
                .formatted(produkt, verfuegbar, gewuenscht));
    }
}
// Verwendung: Gezieltes oder allgemeines Fangen
try {
    shop.bestellen(warenkorb);
} catch (WarenkorbLeerException e) {
    System.out.println("Bitte fuege erst Produkte hinzu!");
} catch (LagerBestandException e) {
    System.out.println("Einige Produkte sind leider ausverkauft.");
} catch (ShopException e) {
    // Faengt alle Shop-Exceptions auf
    System.out.println("Shop-Fehler: " + e.getMessage());
}

Best Practices

1. Vier Standard-Konstruktoren

Professionelle Exceptions haben diese Konstruktoren:

public class MeineException extends RuntimeException {

    // Ohne Nachricht
    public MeineException() {
        super();
    }

    // Mit Nachricht
    public MeineException(String message) {
        super(message);
    }

    // Mit Ursache
    public MeineException(Throwable cause) {
        super(cause);
    }

    // Mit Nachricht und Ursache
    public MeineException(String message, Throwable cause) {
        super(message, cause);
    }
}

2. Exception-Ursache bewahren (Chaining)

// SCHLECHT: Original-Exception geht verloren
try {
    verbindung.oeffnen();
} catch (SQLException e) {
    throw new DatenbankException("Verbindung fehlgeschlagen");
    // Wo genau war der Fehler? Keine Info!
}

// GUT: Original-Exception als Ursache mitgeben
try {
    verbindung.oeffnen();
} catch (SQLException e) {
    throw new DatenbankException("Verbindung fehlgeschlagen", e);
    // e.getCause() liefert die Original-Exception
}

3. Checked vs. Unchecked — Wann was?

// Unchecked (RuntimeException): Programmierfehler, ungueltige Argumente
// --> Aufrufer kann das Problem durch besseren Code vermeiden
throw new IllegalArgumentException("Alter darf nicht negativ sein");
throw new UngueltigesAlterException(alter);

// Checked (Exception): Aeussere Faktoren, die nicht vermeidbar sind
// --> Aufrufer MUSS reagieren
throw new DateiNichtGefundenException(pfad);
throw new VerbindungsFehlerException(url);

4. Aussagekraeftige Nachrichten

// SCHLECHT:
throw new RuntimeException("Fehler");
throw new IllegalArgumentException("Ungueltig");

// GUT:
throw new RuntimeException("Benutzer 'max@mail.com' konnte nicht gespeichert werden");
throw new IllegalArgumentException("Alter muss zwischen 0 und 150 liegen, war: " + alter);

Vergleich mit Python

# Python: Eigene Exception
class AlterUngueltigError(ValueError):
    def __init__(self, alter):
        super().__init__(f"Ungueltiges Alter: {alter}")
        self.alter = alter
// Java: Eigene Exception
public class AlterUngueltigException extends IllegalArgumentException {
    private final int alter;

    public AlterUngueltigException(int alter) {
        super("Ungueltiges Alter: " + alter);
        this.alter = alter;
    }

    public int getAlter() { return alter; }
}

Uebungen

Uebung 1: Passwort-Exception

Erstelle eine UngueltigesPasswortException mit Details, welche Regel verletzt wurde (zu kurz, keine Ziffer, kein Grossbuchstabe).

Uebung 2: Produkt-Exceptions

Erstelle eine Exception-Hierarchie fuer einen Online-Shop: ShopException als Basis, ProduktException, ZahlungsException, LieferException.

Uebung 3: Validierungs-Framework

Erstelle eine ValidationException, die eine Liste von Fehlern speichert:

throw new ValidationException(List.of(
    "Name darf nicht leer sein",
    "Alter muss positiv sein"
));

Uebung 4: Bank-System

Erstelle Exceptions fuer ein Banksystem: KontoNichtGefundenException, KontostandZuNiedrigException, UeberweisungsLimitException. Verwende sie in einer einfachen Bank-Klasse.

Was kommt als Naechstes?

Herzlichen Glueckwunsch — du hast alle theoretischen Grundlagen von Java gelernt! Jetzt kommt der spannendste Teil: Praxis-Projekte! Im naechsten Kapitel baust du einen Taschenrechner und wendest alles an, was du gelernt hast.

Zusammenfassung

  • Eigene Exceptions erstellen durch Erweitern von Exception oder RuntimeException
  • Unchecked (extends RuntimeException): Fangen optional, fuer Programmierfehler
  • Checked (extends Exception): Fangen Pflicht, fuer aeussere Faktoren
  • Eigene Exceptions koennen zusaetzliche Daten speichern (Kontostand, ID, etc.)
  • Exception-Hierarchien ermoglichen gezieltes und allgemeines Fangen
  • Bewahre die Ursache mit dem cause-Parameter (Exception Chaining)
  • Verwende aussagekraeftige Fehlermeldungen mit konkreten Werten

Pro-Tipp: In der Praxis reichen oft die eingebauten Exceptions wie IllegalArgumentException und IllegalStateException voellig aus. Erstelle eigene Exceptions erst, wenn du zusaetzliche Daten mitgeben willst oder eine klare Hierarchie brauchst. Faustregel: Fuer Libraries und Frameworks lohnen sich eigene Exceptions, fuer kleine Projekte reichen die Standard-Exceptions.

Zurück zum Java Kurs