Multi-Catch and Exception Chaining
Catch multiple exception types in one catch block, and chain exceptions to preserve the original cause.
Overview
Key Points
- •catch (IOException | SQLException e) catches either type in one block
- •In a multi-catch, the variable e is implicitly final
- •The types in a multi-catch must not be related by inheritance
- •Exception chaining: throw new MyException("msg", originalEx)
- •getCause() retrieves the original exception; the full chain is in the stack trace
- •Rethrowing: catch (Exception e) { throw e; } re-throws without wrapping
- •catch (Exception e) { throw new RuntimeException(e); } wraps a checked in unchecked
Examples
Multi-catch block
import java.io.IOException;
import java.sql.SQLException;
public class Main {
static void riskyOperation(int choice) throws IOException, SQLException {
if (choice == 1) throw new IOException("File not found");
if (choice == 2) throw new SQLException("Connection lost");
System.out.println("Success for choice " + choice);
}
public static void main(String[] args) {
for (int i = 0; i <= 3; i++) {
try {
riskyOperation(i);
} catch (IOException | SQLException e) {
// One handler for both types
System.out.println("Handled " + e.getClass().getSimpleName() + ": " + e.getMessage());
}
}
}
}Multi-catch avoids duplicating the same handling logic. The variable e is treated as the common supertype of the listed exceptions, so only methods defined on that supertype are available.
Exception chaining across layers
public class Main {
static class ServiceException extends RuntimeException {
ServiceException(String msg, Throwable cause) { super(msg, cause); }
}
static int parseId(String s) {
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
// Translate low-level parsing exception to domain exception
throw new ServiceException("Invalid user ID format: " + s, e);
}
}
static String findUser(String rawId) {
int id = parseId(rawId);
if (id != 42) throw new ServiceException("User not found: " + id, null);
return "Alice";
}
public static void main(String[] args) {
String[] ids = {"42", "abc", "99"};
for (String id : ids) {
try {
System.out.println(findUser(id));
} catch (ServiceException e) {
System.out.println("Service error: " + e.getMessage());
if (e.getCause() != null) {
System.out.println(" Caused by: " + e.getCause().getMessage());
}
}
}
}
}getCause() traverses the chain. The original NumberFormatException is preserved inside the ServiceException, so you can log the root cause while showing a clean domain-level message to callers.
Rethrowing and wrapping checked as unchecked
import java.io.IOException;
import java.util.List;
public class Main {
static List<String> readLines(String path) {
try {
// Simulated: would use Files.readAllLines(Path.of(path))
if (path.equals("missing.txt")) throw new IOException("File missing");
return List.of("line1", "line2");
} catch (IOException e) {
// Wrap checked IOException in unchecked to avoid polluting method signature
throw new RuntimeException("Cannot read file: " + path, e);
}
}
public static void main(String[] args) {
try {
List<String> lines = readLines("missing.txt");
lines.forEach(System.out::println);
} catch (RuntimeException e) {
System.out.println("Error: " + e.getMessage());
System.out.println("Root cause: " + e.getCause().getClass().getSimpleName());
}
// Normal case
readLines("data.txt").forEach(System.out::println);
}
}Wrapping a checked exception in RuntimeException is a common pattern in functional code (streams, lambdas) because lambda bodies cannot throw checked exceptions. Always include the original as the cause.
Common Mistakes
- Listing related types in a multi-catch: catch (Exception | IOException e) is a compile error because IOException is a subtype of Exception. Just catch Exception
- Reassigning the multi-catch variable: since it is implicitly final, you cannot do e = new IOException(). This would cause a compile error
- Wrapping and losing the cause: throw new RuntimeException("msg") without passing the original exception. The original stack trace is lost, making debugging very hard
Note
Use multi-catch when two or more exceptions require identical handling. If the handling differs even slightly, use separate catch blocks. Exception chaining is mandatory when translating between layers: always pass the original exception as the cause.
Exercises
Write Java code and click Run to execute it instantly. When your output looks correct, mark as done.
Completed: 0 / 4 exercises
Knowledge Check
Question 1 of 4