Exception Handling in Java: Try-Catch-Finally, Throw, Throws & Custom Exceptions
This article is a comprehensive guide to Java Exception Handling – including try-catch-finally, throw, throws and custom exceptions with practical examples.
In a Nutshell
Exception Handling enables controlled error handling in Java. try-catch-finally catches exceptions, throw/throws declares them, and custom exceptions enable specific error handling.
Compact Technical Description
Exception Handling is a mechanism for handling errors and abnormal conditions during program execution. Java uses a structured exception hierarchy.
Exception Hierarchy:
- Throwable: Base class of all Errors and Exceptions
- Error: Serious system errors (not recoverable)
- Exception: Recoverable exceptions
- Checked Exceptions: Compile-time check required
- Unchecked Exceptions: Runtime errors (RuntimeException)
Important Concepts:
Try-Catch-Finally
- try: Block with potentially faulty code
- catch: Block for handling specific exceptions
- finally: Block that is always executed (even if exception occurs)
- try-with-resources: Automatic resource cleanup
Throw and Throws
- throw: Explicit throwing of an exception
- throws: Declaration of exceptions in method signature
- rethrow: Passing on exceptions
Custom Exceptions
- Custom Exception Classes: Specific error handling
- Business Exceptions: Domain-specific errors
- Validation Exceptions: Input validation errors
Exam-Relevant Key Points
- Exception Handling: Structured error handling in Java
- Try-Catch-Finally: Basic error handling blocks
- Checked vs Unchecked Exceptions: Compile-time vs runtime errors
- Throw vs Throws: Throwing vs declaring exceptions
- Custom Exceptions: Creating own exception classes
- Exception Hierarchy: Throwable → Error/Exception → RuntimeException
- IHK-relevant: Important for robust, fault-resistant software
Core Components
- Exception Hierarchy: Throwable, Error, Exception, RuntimeException
- Try-Catch-Finally: Error handling structure
- Throw/Throws: Exception throwing and declaration
- Custom Exceptions: Specific error classes
- Try-with-Resources: Automatic resource management
- Exception Chaining: Linking of exceptions
- Best Practices: Guidelines for exception handling
- Logging: Error logging
Practical Examples
1. Basic Exception Handling
import java.io.*;
import java.util.*;
public class ExceptionGrundlagen {
public static void main(String[] args) {
System.out.println("=== Exception Handling Grundlagen ===");
// Einfache try-catch
einfachesTryCatch();
// Mehrere catch-Blöcke
mehrfachCatch();
// Finally-Block
finallyDemo();
// Try-with-Resources
tryWithResourcesDemo();
// Exception-Auslösung
exceptionAusloesen();
}
private static void einfachesTryCatch() {
System.out.println("\n--- Einfaches Try-Catch ---");
try {
// Potentiell fehlerhafter Code
int ergebnis = 10 / 0; // ArithmeticException
System.out.println("Ergebnis: " + ergebnis);
} catch (ArithmeticException e) {
System.out.println("Fehler: Division durch Null");
System.out.println("Exception-Typ: " + e.getClass().getSimpleName());
System.out.println("Nachricht: " + e.getMessage());
}
System.out.println("Programm läuft weiter");
}
private static void mehrfachCatch() {
System.out.println("\n--- Mehrfache Catch-Blöcke ---");
String[] zahlen = {"10", "5", "abc", "2"};
for (String zahlString : zahlen) {
try {
int zahl = Integer.parseInt(zahlString);
int ergebnis = 100 / zahl;
System.out.println("100 / " + zahl + " = " + ergebnis);
} catch (NumberFormatException e) {
System.out.println("Fehler: '" + zahlString + "' ist keine Zahl");
} catch (ArithmeticException e) {
System.out.println("Fehler: Division durch Null bei " + zahlString);
} catch (Exception e) {
// Allgemeiner Exception-Handler
System.out.println("Unerwarteter Fehler: " + e.getMessage());
}
}
}
private static void finallyDemo() {
System.out.println("\n--- Finally-Block Demo ---");
BufferedReader reader = null;
try {
// Ressource öffnen
reader = new BufferedReader(new FileReader("nichtexistente.txt"));
String zeile = reader.readLine();
System.out.println("Gelesen: " + zeile);
} catch (FileNotFoundException e) {
System.out.println("Datei nicht gefunden: " + e.getMessage());
} catch (IOException e) {
System.out.println("Lesefehler: " + e.getMessage());
} finally {
// Wird immer ausgeführt
System.out.println("Finally-Block wird ausgeführt");
if (reader != null) {
try {
reader.close();
System.out.println("Reader geschlossen");
} catch (IOException e) {
System.out.println("Fehler beim Schließen: " + e.getMessage());
}
}
}
}
private static void tryWithResourcesDemo() {
System.out.println("\n--- Try-with-Resources Demo ---");
// Automatische Ressourcenverwaltung
try (BufferedReader reader = new BufferedReader(new FileReader("beispiel.txt"))) {
String zeile;
int zeilenNummer = 1;
while ((zeile = reader.readLine()) != null) {
System.out.println("Zeile " + zeilenNummer + ": " + zeile);
zeilenNummer++;
if (zeilenNummer > 3) {
throw new IOException("Künstlicher Fehler nach 3 Zeilen");
}
}
} catch (FileNotFoundException e) {
System.out.println("Datei nicht gefunden: " + e.getMessage());
} catch (IOException e) {
System.out.println("IO-Fehler: " + e.getMessage());
} finally {
System.out.println("Try-with-Resources beendet (Reader automatisch geschlossen)");
}
}
private static void exceptionAusloesen() {
System.out.println("\n--- Exception-Auslösung ---");
try {
validiereAlter(15); // Löst Exception aus
} catch (IllegalArgumentException e) {
System.out.println("Validierungsfehler: " + e.getMessage());
}
try {
validiereAlter(25); // Kein Fehler
} catch (IllegalArgumentException e) {
System.out.println("Dies sollte nicht passieren: " + e.getMessage());
}
System.out.println("Alter-Validierung abgeschlossen");
}
private static void validiereAlter(int alter) {
if (alter < 18) {
throw new IllegalArgumentException("Alter muss mindestens 18 sein, aber war: " + alter);
}
System.out.println("Alter " + alter + " ist gültig");
}
}
2. Custom Exceptions and throws Clause
// Custom Exception classes
class GeschaeftsException extends Exception {
public GeschaeftsException(String nachricht) {
super(nachricht);
}
public GeschaeftsException(String nachricht, Throwable ursache) {
super(nachricht, ursache);
}
}
class ValidierungsException extends RuntimeException {
private String feld;
public ValidierungsException(String feld, String nachricht) {
super(nachricht);
this.feld = feld;
}
public String getFeld() {
return feld;
}
}
class DatenbankException extends Exception {
private int fehlercode;
public DatenbankException(String nachricht, int fehlercode) {
super(nachricht);
this.fehlercode = fehlercode;
}
public int getFehlercode() {
return fehlercode;
}
}
// Service classes with Exception Handling
class KundenService {
// Method with throws clause
public Kunde findeKunde(int kundenId) throws GeschaeftsException, DatenbankException {
try {
// Simulate database access
if (kundenId < 1000) {
throw new DatenbankException("Invalid customer ID: " + kundenId, 404);
}
if (kundenId == 1234) {
throw new DatenbankException("Database connection failed", 500);
}
// Simulate successful access
return new Kunde(kundenId, "Max Mustermann", "max@example.com");
} catch (SQLException e) {
// Exception chaining - preserve original exception
throw new DatenbankException("Database error during customer search", 503);
}
}
// Method with Custom Exception
public void kundeAnlegen(Kunde kunde) throws GeschaeftsException {
try {
validiereKunde(kunde);
// Simulate business logic
if (kunde.getName().toLowerCase().contains("test")) {
throw new GeschaeftsException("Test customers are not allowed");
}
// Customer would be saved to database here
System.out.println("Customer created: " + kunde.getName());
} catch (ValidierungsException e) {
// Re-throw with additional information
throw new GeschaeftsException("Customer data invalid: " + e.getMessage(), e);
}
}
private void validiereKunde(Kunde kunde) {
if (kunde.getName() == null || kunde.getName().trim().isEmpty()) {
throw new ValidierungsException("name", "Name must not be empty");
}
if (kunde.getEmail() == null || !kunde.getEmail().contains("@")) {
throw new ValidierungsException("email", "Invalid email address");
}
if (kunde.getName().length() > 100) {
throw new ValidierungsException("name", "Name too long (max 100 characters)");
}
}
}
// Data model
class Kunde {
private int id;
private String name;
private String email;
public Kunde(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
// Getter
public int getId() { return id; }
public String getName() { return name; }
public String getEmail() { return email; }
}
// Main class for demo
public class CustomExceptionDemo {
public static void main(String[] args) {
System.out.println("=== Custom Exceptions Demo ===");
KundenService service = new KundenService();
// Demo 1: Successful customer search
try {
Kunde kunde = service.findeKunde(1001);
System.out.println("Customer found: " + kunde.getName());
} catch (GeschaeftsException | DatenbankException e) {
System.out.println("Error during customer search: " + e.getMessage());
}
// Demo 2: Database error
try {
service.findeKunde(999);
} catch (GeschaeftsException e) {
System.out.println("Business error: " + e.getMessage());
} catch (DatenbankException e) {
System.out.println("Database error (Code " + e.getFehlercode() + "): " + e.getMessage());
}
// Demo 3: Validation error when creating customer
try {
Kunde ungueltigerKunde = new Kunde(0, "", "ungueltig@");
service.kundeAnlegen(ungueltigerKunde);
} catch (GeschaeftsException e) {
System.out.println("Error creating customer: " + e.getMessage());
// Display original exception
if (e.getCause() instanceof ValidierungsException) {
ValidierungsException ve = (ValidierungsException) e.getCause();
System.out.println("Validation error in field '" + ve.getFeld() + "'");
}
}
// Demo 4: Business rule exception
try {
Kunde testKunde = new Kunde(0, "Test User", "test@example.com");
service.kundeAnlegen(testKunde);
} catch (GeschaeftsException e) {
System.out.println("Business rule error: " + e.getMessage());
}
// Demo 5: Exception logging
exceptionLoggingDemo();
}
private static void exceptionLoggingDemo() {
System.out.println("\n--- Exception Logging Demo ---");
try {
// Simulate complex operation
komplexeOperation();
} catch (Exception e) {
// Structured logging
logException(e);
}
}
private static void komplexeOperation() throws Exception {
try {
// First operation
ersteOperation();
// Second operation
zweiteOperation();
} catch (IllegalArgumentException e) {
// Pass exception with context information
throw new Exception("Error in complex operation", e);
}
}
private static void ersteOperation() {
if (Math.random() > 0.5) {
throw new IllegalArgumentException("Error in first operation");
}
System.out.println("First operation successful");
}
private static void zweiteOperation() {
if (Math.random() > 0.7) {
throw new IllegalArgumentException("Error in second operation");
}
System.out.println("Second operation successful");
}
private static void logException(Exception e) {
System.out.println("=== Exception Log ===");
System.out.println("Type: " + e.getClass().getSimpleName());
System.out.println("Message: " + e.getMessage());
System.out.println("Stack Trace:");
// Output stack trace
for (StackTraceElement element : e.getStackTrace()) {
System.out.println(" at " + element.getClassName() +
"." + element.getMethodName() +
"(" + element.getFileName() +
":" + element.getLineNumber() + ")");
}
// Log original exception
if (e.getCause() != null) {
System.out.println("Cause: " + e.getCause().getClass().getSimpleName() +
" - " + e.getCause().getMessage());
}
}
}
3. Advanced Exception Handling Patterns
import java.util.*;
import java.util.function.*;
public class AdvancedExceptionPatterns {
// Result Pattern für Fehlerbehandlung ohne Exceptions
static class Result<T> {
private final T value;
private final Exception error;
private final boolean success;
private Result(T value, Exception error, boolean success) {
this.value = value;
this.error = error;
this.success = success;
}
public static <T> Result<T> success(T value) {
return new Result<>(value, null, true);
}
public static <T> Result<T> failure(Exception error) {
return new Result<>(null, error, false);
}
public boolean isSuccess() { return success; }
public boolean isFailure() { return !success; }
public T getValue() {
if (!success) {
throw new IllegalStateException("No value available on error");
}
return value;
}
public Exception getError() {
if (success) {
throw new IllegalStateException("No error on success");
}
return error;
}
public <U> Result<U> map(Function<T, U> mapper) {
if (success) {
try {
return Result.success(mapper.apply(value));
} catch (Exception e) {
return Result.failure(e);
}
} else {
return Result.failure(error);
}
}
public <U> Result<U> flatMap(Function<T, Result<U>> mapper) {
if (success) {
try {
return mapper.apply(value);
} catch (Exception e) {
return Result.failure(e);
}
} else {
return Result.failure(error);
}
}
public T orElse(T defaultValue) {
return success ? value : defaultValue;
}
}
// Exception-Wrapper für Functional Interfaces
@FunctionalInterface
interface CheckedSupplier<T> {
T get() throws Exception;
}
@FunctionalInterface
interface CheckedRunnable {
void run() throws Exception;
}
@FunctionalInterface
interface CheckedFunction<T, R> {
R apply(T t) throws Exception;
}
// Utility methods for exception handling
static class ExceptionUtils {
public static <T> Optional<T> optionalOf(CheckedSupplier<T> supplier) {
try {
return Optional.ofNullable(supplier.get());
} catch (Exception e) {
return Optional.empty();
}
}
public static <T> Result<T> resultOf(CheckedSupplier<T> supplier) {
try {
return Result.success(supplier.get());
} catch (Exception e) {
return Result.failure(e);
}
}
public static void unchecked(CheckedRunnable runnable) {
try {
runnable.run();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static <T, R> Function<T, R> uncheckedFunction(CheckedFunction<T, R> function) {
return t -> {
try {
return function.apply(t);
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
public static <T> Supplier<T> uncheckedSupplier(CheckedSupplier<T> supplier) {
return () -> {
try {
return supplier.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
}
// Retry mechanism
static class RetryUtils {
public static <T> T retry(int maxAttempts, long delayMs, CheckedSupplier<T> supplier)
throws Exception {
Exception lastException = null;
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return supplier.get();
} catch (Exception e) {
lastException = e;
if (attempt == maxAttempts) {
break;
}
System.out.println("Attempt " + attempt + " failed, retry in " + delayMs + "ms");
Thread.sleep(delayMs);
}
}
throw new Exception("All " + maxAttempts + " attempts failed", lastException);
}
public static <T> Optional<T> retryOptional(int maxAttempts, long delayMs,
CheckedSupplier<T> supplier) {
try {
return Optional.of(retry(maxAttempts, delayMs, supplier));
} catch (Exception e) {
return Optional.empty();
}
}
}
// Service classes with advanced patterns
static class DatabaseService {
// With Result Pattern
public Result<String> readDataWithResult(String id) {
try {
// Simulate database access
if (id == null || id.isEmpty()) {
return Result.failure(new IllegalArgumentException("ID must not be empty"));
}
if (id.equals("error")) {
return Result.failure(new RuntimeException("Database error"));
}
String data = "Data for " + id;
return Result.success(data);
} catch (Exception e) {
return Result.failure(e);
}
}
// With Optional
public Optional<String> readDataWithOptional(String id) {
return ExceptionUtils.optionalOf(() -> {
if (id == null || id.isEmpty()) {
throw new IllegalArgumentException("ID must not be empty");
}
if (id.equals("notfound")) {
return null;
}
return "Data for " + id;
});
}
// With Retry
public String readDataWithRetry(String id) throws Exception {
return RetryUtils.retry(3, 1000, () -> {
// Simulate unstable connection
if (Math.random() > 0.7) {
throw new RuntimeException("Connection error");
}
return "Stable data for " + id;
});
}
}
public static void main(String[] args) {
System.out.println("=== Advanced Exception Patterns ===");
DatabaseService service = new DatabaseService();
// Result Pattern Demo
System.out.println("\n--- Result Pattern Demo ---");
Result<String> result1 = service.readDataWithResult("123");
System.out.println("Successful: " + result1.isSuccess());
result1.ifPresent(value -> System.out.println("Value: " + value));
Result<String> result2 = service.readDataWithResult("error");
System.out.println("Successful: " + result2.isSuccess());
result2.ifPresentOrElse(
value -> System.out.println("Value: " + value),
error -> System.out.println("Error: " + error.getMessage())
);
// Result Chaining
Result<Integer> length = result1
.map(String::length)
.map(len -> len * 2);
System.out.println("Doubled length: " + length.orElse(0));
// Optional Pattern Demo
System.out.println("\n--- Optional Pattern Demo ---");
Optional<String> opt1 = service.readDataWithOptional("123");
opt1.ifPresent(data -> System.out.println("Found: " + data));
Optional<String> opt2 = service.readDataWithOptional("notfound");
System.out.println("Found: " + opt2.isPresent());
// Optional with default
String result = opt2.orElse("Default value");
System.out.println("Result: " + result);
// Retry Demo
System.out.println("\n--- Retry Demo ---");
try {
String data = service.readDataWithRetry("123");
System.out.println("Success after retry: " + data);
} catch (Exception e) {
System.out.println("All retries failed: " + e.getMessage());
}
// Exception Utils Demo
System.out.println("\n--- Exception Utils Demo ---");
// Unchecked Functional Interface
List<String> numbers = Arrays.asList("10", "5", "abc", "2");
numbers.stream()
.map(ExceptionUtils.uncheckedFunction(number -> Integer.parseInt(number) * 2))
.forEach(result -> System.out.println("Doubled: " + result));
// Exception logging with context
System.out.println("\n--- Exception with context ---");
try {
computeComplex(10, 0);
} catch (Exception e) {
logWithContext(e, Map.of(
"operation", "computeComplex",
"param1", 10,
"param2", 0,
"timestamp", System.currentTimeMillis()
));
}
}
private static int computeComplex(int a, int b) throws Exception {
if (b == 0) {
throw new ArithmeticException("Division by zero");
}
return a / b;
}
private static void logWithContext(Exception e, Map<String, Object> context) {
System.out.println("=== Exception with context ===");
System.out.println("Exception: " + e.getClass().getSimpleName());
System.out.println("Message: " + e.getMessage());
System.out.println("Context:");
context.forEach((key, value) ->
System.out.println(" " + key + ": " + value));
System.out.println("Stack Trace:");
Arrays.stream(e.getStackTrace())
.limit(3)
.forEach(element ->
System.out.println(" at " + element.getClassName() +
"." + element.getMethodName()));
}
// Helper method for Result
private static <T> void ifPresent(Result<T> result, Consumer<T> action) {
if (result.isSuccess()) {
action.accept(result.getValue());
}
}
}
Exception Hierarchy Overview
Throwable
├── Error (System errors, not handleable)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── VirtualMachineError
└── Exception (Handleable exceptions)
├── Checked Exceptions (Compile-time check)
│ ├── IOException
│ │ ├── FileNotFoundException
│ │ └── SQLException
│ ├── ClassNotFoundException
│ └── InterruptedException
└── RuntimeException (Unchecked Exceptions)
├── NullPointerException
├── IllegalArgumentException
├── ArithmeticException
├── IndexOutOfBoundsException
└── NumberFormatException
Best Practices for Exception Handling
DO’s
- Catch specific exceptions: Instead of always
Exception - Clean up resources: With finally or try-with-resources
- Meaningful messages: Clear error descriptions
- Exception chaining: Preserve the original exception
- Logging: Log exceptions with context
DON’Ts
- Empty catch blocks: Ignoring exceptions
- Exception swallowing: Suppressing errors
- Over-catch: Too general exceptions
- Return null: Instead of Optional or Result
- PrintStackTrace: In production code
Try-with-Resources vs Finally
Try-with-Resources (Modern)
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
// Resources are automatically closed
} catch (IOException e) {
// Exception handling
}
Finally-Block (Traditional)
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("file.txt"));
// Work with reader
} catch (IOException e) {
// Exception handling
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
// Handle closing errors
}
}
}
Exception Handling Patterns
Template Method Pattern
public abstract class DatabaseTemplate {
public final Result<T> execute(CheckedSupplier<T> operation) {
try {
Connection conn = getConnection();
try {
return Result.success(operation.get());
} finally {
conn.close();
}
} catch (Exception e) {
return Result.failure(e);
}
}
protected abstract Connection getConnection() throws SQLException;
}
Decorator Pattern
public class RetryDecorator<T> implements Supplier<T> {
private final Supplier<T> supplier;
private final int maxRetries;
public RetryDecorator(Supplier<T> supplier, int maxRetries) {
this.supplier = supplier;
this.maxRetries = maxRetries;
}
@Override
public T get() {
Exception lastException = null;
for (int i = 0; i <= maxRetries; i++) {
try {
return supplier.get();
} catch (Exception e) {
lastException = e;
if (i < maxRetries) {
// Wait before retry
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException(ie);
}
}
}
}
throw new RuntimeException("All retries failed", lastException);
}
}
Advantages and Disadvantages
Advantages of Exception Handling
- Error control: Structured error handling
- Code clarity: Separation of normal and error cases
- Robustness: Stable programs even with errors
- Debugging: Better error analysis through stack traces
- Maintainability: Centralized error handling
Disadvantages
- Performance: Exception handling has overhead
- Complexity: Nested try-catch structures
- Overhead: More code for error handling
- Misuse: Using exceptions for control flow
Frequently Asked Exam Questions
-
What is the difference between checked and unchecked exceptions? Checked exceptions must be declared/handled, unchecked ones do not (RuntimeException).
-
When is finally executed? Always, regardless of whether an exception occurs or not, even with return in the try block.
-
Explain try-with-resources! Automatic resource cleanup for objects that implement AutoCloseable.
-
What is exception chaining? Propagating exceptions while preserving the original cause.
Most Important Sources
- https://docs.oracle.com/javase/tutorial/essential/exceptions/
- https://www.baeldung.com/java-exceptions
- https://effectivejava.com/