Rethrowing Exceptions

Introduction

Rethrowing means catching an exception and throwing it again to be handled at a higher level.


Why Rethrow?

  1. Log and propagate - log locally, handle elsewhere
  2. Partial handling - do some cleanup, let caller finish
  3. Add context - wrap with more information
  4. Chain responsibility - multiple handlers

Basic Syntax

try {
    // code
} catch (Exception e) {
    // Log or partial handling
    throw e;  // Rethrow same exception
}

Simple Example

class Demo {
    void method1() throws Exception {
        throw new Exception("Error in method1");
    }

    void method2() throws Exception {
        try {
            method1();
        } catch (Exception e) {
            System.out.println("Caught in method2: " + e.getMessage());
            throw e;  // Rethrow
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Demo d = new Demo();
        try {
            d.method2();
        } catch (Exception e) {
            System.out.println("Caught in main: " + e.getMessage());
        }
    }
}

Output:

Caught in method2: Error in method1
Caught in main: Error in method1

Log and Rethrow

import java.io.*;

class FileProcessor {
    void processFile(String filename) throws IOException {
        try {
            FileReader fr = new FileReader(filename);
            // Process file
        } catch (IOException e) {
            // Log the error
            System.err.println("Error processing file: " + filename);
            System.err.println("Error: " + e.getMessage());

            // Rethrow for caller to handle
            throw e;
        }
    }
}

public class Main {
    public static void main(String[] args) {
        FileProcessor processor = new FileProcessor();
        try {
            processor.processFile("missing.txt");
        } catch (IOException e) {
            System.out.println("Main: File operation failed");
        }
    }
}

Wrapping Exception

Wrap original exception in new one with more context.

import java.io.*;

class ApplicationException extends Exception {
    ApplicationException(String message, Throwable cause) {
        super(message, cause);
    }
}

class DataLoader {
    void loadData(String filename) throws ApplicationException {
        try {
            FileReader fr = new FileReader(filename);
            // Load data
        } catch (IOException e) {
            // Wrap in application-specific exception
            throw new ApplicationException("Failed to load data from: " + filename, e);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        DataLoader loader = new DataLoader();
        try {
            loader.loadData("data.txt");
        } catch (ApplicationException e) {
            System.out.println("Application error: " + e.getMessage());
            System.out.println("Caused by: " + e.getCause());
        }
    }
}

Exception Chaining

Link exceptions to show cause.

class Level3 {
    void method() throws Exception {
        throw new Exception("Level 3 error");
    }
}

class Level2 {
    void method() throws Exception {
        try {
            new Level3().method();
        } catch (Exception e) {
            throw new Exception("Level 2 error", e);  // Chain
        }
    }
}

class Level1 {
    void method() throws Exception {
        try {
            new Level2().method();
        } catch (Exception e) {
            throw new Exception("Level 1 error", e);  // Chain
        }
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            new Level1().method();
        } catch (Exception e) {
            System.out.println("Main caught: " + e.getMessage());

            // Trace the chain
            Throwable cause = e.getCause();
            while (cause != null) {
                System.out.println("Caused by: " + cause.getMessage());
                cause = cause.getCause();
            }
        }
    }
}

Output:

Main caught: Level 1 error
Caused by: Level 2 error
Caused by: Level 3 error

Cleanup and Rethrow

import java.io.*;

class DatabaseConnection {
    void connect() throws Exception {
        System.out.println("Connecting...");
        throw new Exception("Connection failed");
    }

    void close() {
        System.out.println("Connection closed");
    }
}

class DataService {
    void fetchData() throws Exception {
        DatabaseConnection conn = new DatabaseConnection();
        try {
            conn.connect();
            // Fetch data
        } catch (Exception e) {
            // Cleanup before rethrowing
            conn.close();
            System.out.println("Cleaned up resources");
            throw e;  // Rethrow
        }
    }
}

public class Main {
    public static void main(String[] args) {
        DataService service = new DataService();
        try {
            service.fetchData();
        } catch (Exception e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}

Complete Example: Transaction

class InsufficientFundsException extends Exception {
    InsufficientFundsException(String message) {
        super(message);
    }
}

class TransactionException extends Exception {
    TransactionException(String message, Throwable cause) {
        super(message, cause);
    }
}

class BankAccount {
    private double balance;

    BankAccount(double balance) {
        this.balance = balance;
    }

    void withdraw(double amount) throws InsufficientFundsException {
        if (amount > balance) {
            throw new InsufficientFundsException("Balance: $" + balance + ", Requested: $" + amount);
        }
        balance -= amount;
        System.out.println("Withdrawn: $" + amount);
    }

    double getBalance() {
        return balance;
    }
}

class TransactionService {
    void processWithdrawal(BankAccount account, double amount) throws TransactionException {
        try {
            System.out.println("Processing withdrawal...");
            account.withdraw(amount);
            System.out.println("Transaction successful");
        } catch (InsufficientFundsException e) {
            // Log transaction failure
            System.out.println("Transaction failed: " + e.getMessage());

            // Wrap and rethrow with context
            throw new TransactionException("Withdrawal failed for amount: $" + amount, e);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount(1000);
        TransactionService service = new TransactionService();

        try {
            service.processWithdrawal(account, 500);   // Success
            service.processWithdrawal(account, 1000);  // Fail
        } catch (TransactionException e) {
            System.out.println("\nMain handler:");
            System.out.println("Error: " + e.getMessage());
            System.out.println("Root cause: " + e.getCause().getMessage());
        }
    }
}

Conditional Rethrowing

import java.io.*;

class FileHandler {
    void readFile(String filename) throws IOException {
        try {
            FileReader fr = new FileReader(filename);
            // Read file
        } catch (FileNotFoundException e) {
            // Handle file not found differently
            System.out.println("Creating new file: " + filename);
            // Don't rethrow - handled
        } catch (IOException e) {
            // Other IO errors - rethrow
            System.out.println("Unexpected IO error");
            throw e;
        }
    }
}

public class Main {
    public static void main(String[] args) {
        FileHandler handler = new FileHandler();
        try {
            handler.readFile("test.txt");
        } catch (IOException e) {
            System.out.println("Main: " + e.getMessage());
        }
    }
}

Rethrowing vs New Exception

Rethrow Same:

try {
    // code
} catch (Exception e) {
    System.out.println("Log: " + e.getMessage());
    throw e;  // Same exception, same stack trace
}

Throw New:

try {
    // code
} catch (Exception e) {
    throw new Exception("New error");  // New exception, new stack trace
}

Wrap (Best):

try {
    // code
} catch (Exception e) {
    throw new MyException("Context", e);  // Preserves original
}

getCause() Method

public class Main {
    public static void main(String[] args) {
        try {
            method1();
        } catch (Exception e) {
            System.out.println("Exception: " + e.getMessage());

            Throwable cause = e.getCause();
            if (cause != null) {
                System.out.println("Cause: " + cause.getMessage());
            }
        }
    }

    static void method1() throws Exception {
        try {
            int result = 10 / 0;
        } catch (ArithmeticException e) {
            throw new Exception("Calculation failed", e);
        }
    }
}

When to Rethrow

Rethrow when:

  • Need to log but can’t fully handle
  • Cleanup required before propagating
  • Adding context to exception
  • Multiple levels need to handle

Don’t rethrow when:

  • Can fully handle the error
  • No value in propagating
  • Already at top level

Best Practices

  1. Log before rethrowing
  2. Wrap with context
  3. Use exception chaining
  4. Clean up resources
  5. Don’t lose original exception
  6. Document rethrown exceptions

Quick Reference

// Simple rethrow
try {
    // code
} catch (Exception e) {
    System.out.println("Logging: " + e.getMessage());
    throw e;  // Rethrow
}

// Wrap and rethrow
try {
    // code
} catch (IOException e) {
    throw new MyException("Context", e);
}

// Conditional rethrow
try {
    // code
} catch (Exception e) {
    if (canHandle(e)) {
        handle(e);
    } else {
        throw e;  // Can't handle, rethrow
    }
}

// Get cause
try {
    // code
} catch (Exception e) {
    Throwable cause = e.getCause();
    while (cause != null) {
        System.out.println(cause.getMessage());
        cause = cause.getCause();
    }
}

Common Mistakes

// ✗ Wrong - losing original exception
try {
    // code
} catch (IOException e) {
    throw new Exception("Error");  // Lost original!
}

// ✓ Correct - preserve original
try {
    // code
} catch (IOException e) {
    throw new Exception("Error", e);  // Chained
}

// ✗ Wrong - empty log and rethrow
try {
    // code
} catch (Exception e) {
    // Nothing here
    throw e;  // Why catch if doing nothing?
}

// ✓ Correct - add value
try {
    // code
} catch (Exception e) {
    System.err.println("Context: " + someInfo);
    cleanupResources();
    throw e;
}

Exam Tips

Remember:

  1. Rethrow = catch and throw again
  2. throw e to rethrow same
  3. Wrap for context
  4. getCause() retrieves original
  5. Chain exceptions for tracing
  6. Log before rethrowing
  7. Cleanup before rethrowing
  8. Preserve original exception
  9. Must declare rethrown checked exceptions
  10. Document exception propagation

Common Questions:

  • What is rethrowing?
  • How to rethrow exception?
  • Rethrowing vs new exception?
  • Exception chaining?
  • getCause() method?
  • When to rethrow?
  • Wrapping exceptions?
  • Best practices?
  • Preserving stack trace?
  • Why rethrow?