Skip to content
IRC-CodingIRC-Coding
Exception Handling JavaTry Catch FinallyThrow ThrowsCustom ExceptionsError HandlingProgramming Language

Exception Handling in Java: Try-Catch-Finally & Custom Exceptions

Master Java exception handling: try-catch-finally, throw, throws, custom exceptions. Error handling best practices for robust software.

S

schutzgeist

15 min read
Exception Handling in Java: Try-Catch-Finally & Custom Exceptions

Exception Handling in Java: Try-Catch-Finally, Throw, Throws & Custom Exceptions

This guide covers exception handling in Java comprehensively—including try-catch-finally, throw, throws, and custom exceptions with practical examples.

In a Nutshell

Exception handling enables controlled error management in Java. try-catch-finally blocks catch exceptions, throw/throws declare them, and custom exceptions allow domain-specific error handling.

Core Concepts

Exception handling is a mechanism for dealing with errors and abnormal conditions during program execution. Java uses a structured exception hierarchy.

Exception hierarchy:

  • Throwable: Base class for all errors and exceptions
  • Error: Severe system errors (not recoverable)
  • Exception: Recoverable exceptions
    • Checked Exceptions: Verified at compile time
    • Unchecked Exceptions: Runtime errors (RuntimeException)

Key concepts:

Try-Catch-Finally

  • try: Block containing potentially problematic code
  • catch: Block handling specific exceptions
  • finally: Block executed regardless of exceptions
  • try-with-resources: Automatic resource cleanup

Throw and Throws

  • throw: Explicitly raise an exception
  • throws: Declare exceptions in method signature
  • rethrow: Pass exceptions up the call stack

Custom Exceptions

  • Custom exception classes: Domain-specific error handling
  • Business exceptions: Domain logic errors
  • Validation exceptions: Input validation failures

Key Points

  • Exception handling: Structured error management in Java
  • Try-catch-finally: Fundamental error handling blocks
  • Checked vs unchecked exceptions: Compile-time vs runtime errors
  • Throw vs throws: Raising vs declaring exceptions
  • Custom exceptions: Creating application-specific exception classes
  • Exception hierarchy: Throwable → Error/Exception → RuntimeException
  • Production relevance: Essential for robust, fault-tolerant software

Core Components

  1. Exception hierarchy: Throwable, Error, Exception, RuntimeException
  2. Try-catch-finally: Error handling structure
  3. Throw/throws: Exception raising and declaration
  4. Custom exceptions: Application-specific error classes
  5. Try-with-resources: Automatic resource management
  6. Exception chaining: Linking exceptions together
  7. Best practices: Exception handling guidelines
  8. Logging: Error logging and reporting

Code 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 BusinessException extends Exception {
    public BusinessException(String message) {
        super(message);
    }
    
    public BusinessException(String message, Throwable cause) {
        super(message, cause);
    }
}

class ValidationException extends RuntimeException {
    private String field;
    
    public ValidationException(String field, String message) {
        super(message);
        this.field = field;
    }
    
    public String getField() {
        return field;
    }
}

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

// Service classes with exception handling
class CustomerService {
    
    // Method with throws clause
    public Customer findCustomer(int customerId) throws BusinessException, DatabaseException {
        try {
            // Simulate database access
            if (customerId < 1000) {
                throw new DatabaseException("Invalid customer ID: " + customerId, 404);
            }
            
            if (customerId == 1234) {
                throw new DatabaseException("Database connection failed", 500);
            }
            
            // Simulate successful access
            return new Customer(customerId, "John Doe", "john@example.com");
            
        } catch (SQLException e) {
            // Exception chaining - preserve original exception
            throw new DatabaseException("Database error during customer lookup", 503);
        }
    }
    
    // Method with custom exception
    public void createCustomer(Customer customer) throws BusinessException {
        try {
            validateCustomer(customer);
            
            // Simulate business logic
            if (customer.getName().toLowerCase().contains("test")) {
                throw new BusinessException("Test customers cannot be created");
            }
            
            // Customer would be saved to database here
            System.out.println("Customer created: " + customer.getName());
            
        } catch (ValidationException e) {
            // Re-throw with additional information
            throw new BusinessException("Invalid customer data: " + e.getMessage(), e);
        }
    }
    
    private void validateCustomer(Customer customer) {
        if (customer.getName() == null || customer.getName().trim().isEmpty()) {
            throw new ValidationException("name", "Name cannot be empty");
        }
        
        if (customer.getEmail() == null || !customer.getEmail().contains("@")) {
            throw new ValidationException("email", "Invalid email address");
        }
        
        if (customer.getName().length() > 100) {
            throw new ValidationException("name", "Name too long (max 100 characters)");
        }
    }
}

// Data model
class Customer {
    private int id;
    private String name;
    private String email;
    
    public Customer(int id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }
    
    // Getters
    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 ===");
        
        CustomerService service = new CustomerService();
        
        // Demo 1: Successful customer lookup
        try {
            Customer customer = service.findCustomer(1001);
            System.out.println("Customer found: " + customer.getName());
            
        } catch (BusinessException | DatabaseException e) {
            System.out.println("Error during customer lookup: " + e.getMessage());
        }
        
        // Demo 2: Database error
        try {
            service.findCustomer(999);
            
        } catch (BusinessException e) {
            System.out.println("Business error: " + e.getMessage());
            
        } catch (DatabaseException e) {
            System.out.println("Database error (Code " + e.getErrorCode() + "): " + e.getMessage());
        }
        
        // Demo 3: Validation error during creation
        try {
            Customer invalidCustomer = new Customer(0, "", "invalid@");
            service.createCustomer(invalidCustomer);
            
        } catch (BusinessException e) {
            System.out.println("Error creating customer: " + e.getMessage());
            
            // Display root exception
            if (e.getCause() instanceof ValidationException) {
                ValidationException ve = (ValidationException) e.getCause();
                System.out.println("Validation error in field '" + ve.getField() + "'");
            }
        }
        
        // Demo 4: Business rule exception
        try {
            Customer testCustomer = new Customer(0, "Test User", "test@example.com");
            service.createCustomer(testCustomer);
            
        } catch (BusinessException 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
            complexOperation();
            
        } catch (Exception e) {
            // Structured logging
            logException(e);
        }
    }
    
    private static void complexOperation() throws Exception {
        try {
            // First operation
            firstOperation();
            
            // Second operation
            secondOperation();
            
        } catch (IllegalArgumentException e) {
            // Forward exception with context information
            throw new Exception("Error in complex operation", e);
        }
    }
    
    private static void firstOperation() {
        if (Math.random() > 0.5) {
            throw new IllegalArgumentException("Error in first operation");
        }
        System.out.println("First operation successful");
    }
    
    private static void secondOperation() {
        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:");
        
        // Print stack trace
        for (StackTraceElement element : e.getStackTrace()) {
            System.out.println("  at " + element.getClassName() + 
                             "." + element.getMethodName() + 
                             "(" + element.getFileName() + 
                             ":" + element.getLineNumber() + ")");
        }
        
        // Log root 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("Kein Wert bei Fehler vorhanden");
            }
            return value;
        }
        
        public Exception getError() {
            if (success) {
                throw new IllegalStateException("Kein Fehler bei Erfolg vorhanden");
            }
            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-Methoden für 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-Mechanismus
    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("Versuch " + attempt + " fehlgeschlagen, retry in " + delayMs + "ms");
                    Thread.sleep(delayMs);
                }
            }
            
            throw new Exception("Alle " + maxAttempts + " Versuche fehlgeschlagen", 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-Klassen mit fortgeschrittenen Patterns
    static class DatenbankService {
        
        // Mit Result Pattern
        public Result<String> leseDatenMitResult(String id) {
            try {
                // Simuliere Datenbankzugriff
                if (id == null || id.isEmpty()) {
                    return Result.failure(new IllegalArgumentException("ID darf nicht leer sein"));
                }
                
                if (id.equals("error")) {
                    return Result.failure(new RuntimeException("Datenbankfehler"));
                }
                
                String daten = "Daten für " + id;
                return Result.success(daten);
                
            } catch (Exception e) {
                return Result.failure(e);
            }
        }
        
        // Mit Optional
        public Optional<String> leseDatenMitOptional(String id) {
            return ExceptionUtils.optionalOf(() -> {
                if (id == null || id.isEmpty()) {
                    throw new IllegalArgumentException("ID darf nicht leer sein");
                }
                
                if (id.equals("notfound")) {
                    return null;
                }
                
                return "Daten für " + id;
            });
        }
        
        // Mit Retry
        public String leseDatenMitRetry(String id) throws Exception {
            return RetryUtils.retry(3, 1000, () -> {
                // Simuliere instabile Verbindung
                if (Math.random() > 0.7) {
                    throw new RuntimeException("Verbindungsfehler");
                }
                
                return "Stabile Daten für " + id;
            });
        }
    }
    
    public static void main(String[] args) {
        System.out.println("=== Fortgeschrittene Exception Patterns ===");
        
        DatenbankService service = new DatenbankService();
        
        // Result Pattern Demo
        System.out.println("\n--- Result Pattern Demo ---");
        
        Result<String> result1 = service.leseDatenMitResult("123");
        System.out.println("Erfolgreich: " + result1.isSuccess());
        result1.ifPresent(value -> System.out.println("Wert: " + value));
        
        Result<String> result2 = service.leseDatenMitResult("error");
        System.out.println("Erfolgreich: " + result2.isSuccess());
        result2.ifPresentOrElse(
            value -> System.out.println("Wert: " + value),
            error -> System.out.println("Fehler: " + error.getMessage())
        );
        
        // Result Chaining
        Result<Integer> laenge = result1
            .map(String::length)
            .map(laeng -> laeng * 2);
        
        System.out.println("Verdoppelte Länge: " + laenge.orElse(0));
        
        // Optional Pattern Demo
        System.out.println("\n--- Optional Pattern Demo ---");
        
        Optional<String> opt1 = service.leseDatenMitOptional("123");
        opt1.ifPresent(daten -> System.out.println("Gefunden: " + daten));
        
        Optional<String> opt2 = service.leseDatenMitOptional("notfound");
        System.out.println("Gefunden: " + opt2.isPresent());
        
        // Optional mit Default
        String ergebnis = opt2.orElse("Standardwert");
        System.out.println("Ergebnis: " + ergebnis);
        
        // Retry Demo
        System.out.println("\n--- Retry Demo ---");
        
        try {
            String daten = service.leseDatenMitRetry("123");
            System.out.println("Erfolg nach Retry: " + daten);
            
        } catch (Exception e) {
            System.out.println("Alle Retrys fehlgeschlagen: " + e.getMessage());
        }
        
        // Exception Utils Demo
        System.out.println("\n--- Exception Utils Demo ---");
        
        // Unchecked Functional Interface
        List<String> zahlen = Arrays.asList("10", "5", "abc", "2");
        
        zahlen.stream()
            .map(ExceptionUtils.uncheckedFunction(zahl -> Integer.parseInt(zahl) * 2))
            .forEach(ergebnis -> System.out.println("Verdoppelt: " + ergebnis));
        
        // Exception Logging mit Context
        System.out.println("\n--- Exception mit Context ---");
        
        try {
            berechneKomplex(10, 0);
            
        } catch (Exception e) {
            logWithContext(e, Map.of(
                "operation", "berechneKomplex",
                "param1", 10,
                "param2", 0,
                "timestamp", System.currentTimeMillis()
            ));
        }
    }
    
    private static int berechneKomplex(int a, int b) throws Exception {
        if (b == 0) {
            throw new ArithmeticException("Division durch Null");
        }
        
        return a / b;
    }
    
    private static void logWithContext(Exception e, Map<String, Object> context) {
        System.out.println("=== Exception mit Context ===");
        System.out.println("Exception: " + e.getClass().getSimpleName());
        System.out.println("Nachricht: " + 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()));
    }
    
    // Hilfsmethode für Result
    private static <T> void ifPresent(Result<T> result, Consumer<T> action) {
        if (result.isSuccess()) {
            action.accept(result.getValue());
        }
    }
}

The Result pattern provides a cleaner alternative to throwing exceptions everywhere. Instead of relying on try-catch blocks, you wrap your return value alongside success or failure information. This makes error handling explicit and composable—you can chain operations with map() and flatMap() just like you would with Optional.

When should you use Result over exceptions? Use Result when you’re building functional pipelines or when failure is a normal part of your logic. Use traditional exceptions for truly exceptional conditions that you can’t recover from gracefully.

The checked exception wrappers—CheckedSupplier, CheckedFunction, and CheckedRunnable—solve a real problem in Java: standard functional interfaces don’t throw checked exceptions. This forces you to catch and wrap them. The utility methods in ExceptionUtils do that wrapping for you automatically.

The retry mechanism demonstrates a practical pattern for handling transient failures. Network calls, database queries, and external API requests often fail temporarily. Rather than failing immediately, you attempt the operation multiple times with exponential backoff. The delay between retries gives the system time to recover.

Context-aware logging transforms cryptic error messages into actionable information. Instead of just seeing an exception, you capture the parameters, operation name, and timestamp that led to the failure. This dramatically speeds up debugging, especially in production environments where you can’t reproduce issues on demand.

The examples show how these patterns work together: the database service uses Result internally, but exposes Optional for simpler use cases. The retry wrapper composes with both. This layered approach gives you flexibility—consume errors the way that makes sense for your code.

Exception Hierarchy Overview

Throwable
├── Error (System failures, not recoverable)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── VirtualMachineError
└── Exception (Recoverable exceptions)
    ├── Checked Exceptions (Compile-time checking)
    │   ├── IOException
    │   │   ├── FileNotFoundException
    │   │   └── SQLException
    │   ├── ClassNotFoundException
    │   └── InterruptedException
    └── RuntimeException (Unchecked Exceptions)
        ├── NullPointerException
        ├── IllegalArgumentException
        ├── ArithmeticException
        ├── IndexOutOfBoundsException
        └── NumberFormatException

Exception Handling Best Practices

DO’s

  • Catch specific exceptions: Rather than always catching Exception
  • Clean up resources: Use finally or try-with-resources
  • Write meaningful messages: Provide clear error descriptions
  • Chain exceptions: Preserve the original cause
  • Log exceptions: Record them with context

DON’Ts

  • Empty catch blocks: Silently ignoring exceptions
  • Swallow exceptions: Suppressing errors without handling
  • Over-catch: Catching overly broad exception types
  • Return null: Use Optional or Result instead
  • printStackTrace in production: Never call 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 close error
        }
    }
}

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 management
  • Code clarity: Separates normal flow from error cases
  • Robustness: Stable programs that handle failures gracefully
  • Debugging: Better error analysis through stack traces
  • Maintainability: Centralized error handling

Disadvantages

  • Performance: Exception handling carries overhead
  • Complexity: Nested try-catch structures can become unwieldy
  • Code overhead: More lines needed for error handling
  • Misuse: Using exceptions for control flow

Common Exam Questions

  1. What’s the difference between checked and unchecked exceptions? Checked exceptions must be declared or handled, unchecked exceptions do not (RuntimeException subclasses).

  2. When is finally executed? Always, whether an exception occurs or not, even if there’s a return statement in the try block.

  3. Explain try-with-resources! Automatic resource cleanup for objects implementing AutoCloseable.

  4. What is exception chaining? Propagating an exception while preserving the original cause.

Key Resources

  1. https://docs.oracle.com/javase/tutorial/essential/exceptions/
  2. https://www.baeldung.com/java-exceptions
  3. https://effectivejava.com/
Back to Blog
Share:

Related Posts