Exceptions

77. Multi-catch and Rethrowing

Multi-catch catch (A | B e) handles unrelated exceptions together; rethrowing after catching allows logging + propagation; precise rethrow type inference (Java 7+) lets you declare only the specific thrown types even when rethrowing from a broad catch.

Java 7+ (multi-catch, rethrow); Java 21 (core language)

Explanation

catch (IOException | SQLException e) catches either type. The variable e is implicitly final and its type is the union โ€” you can only call methods that exist on both. This avoids duplicated catch bodies when the handling logic is identical.

Rethrowing: catch, log or augment, then rethrow the same exception (throw e). This preserves the original stack trace. Wrapping: throw new RuntimeException("context", e) adds context while preserving the cause chain.

Precise rethrow (Java 7+): if a catch (Exception e) block only rethrows via throw e, and the compiler can prove only specific subtypes were thrown in the try block, the throws declaration can list just those subtypes โ€” not Exception. This makes API signatures more precise.

Example

import java.io.*;
import java.sql.SQLException;

public class MultiCatchAndRethrow {
    static void riskyOperation(int choice) throws IOException, SQLException {
        switch (choice) {
            case 0 -> throw new IOException("File error");
            case 1 -> throw new SQLException("DB error");
            default -> System.out.println("OK");
        }
    }

    // Multi-catch: identical handling for two unrelated exceptions
    static void handleMulti(int choice) {
        try {
            riskyOperation(choice);
        } catch (IOException | SQLException e) {
            // e is final; call methods common to both
            System.out.println("Caught: " + e.getClass().getSimpleName() + ": " + e.getMessage());
        }
    }

    // Rethrow after logging: preserves original stack trace
    static void logAndRethrow(int choice) throws IOException, SQLException {
        try {
            riskyOperation(choice);
        } catch (IOException | SQLException e) {
            System.out.println("Logging: " + e.getMessage());
            throw e; // rethrow same exception โ€” stack trace unchanged
        }
    }

    // Wrap in RuntimeException for translation (checked -> unchecked)
    static void wrapAndRethrow(int choice) {
        try {
            riskyOperation(choice);
        } catch (IOException | SQLException e) {
            throw new RuntimeException("Operation failed", e); // cause chain preserved
        }
    }

    public static void main(String[] args) {
        handleMulti(0);
        handleMulti(1);

        try { logAndRethrow(0); } catch (IOException | SQLException e) {
            System.out.println("Re-caught: " + e.getMessage());
        }

        try { wrapAndRethrow(1); } catch (RuntimeException e) {
            System.out.println("Wrapped: " + e.getMessage());
            System.out.println("Cause:   " + e.getCause().getClass().getSimpleName());
        }
    }
}

Sources