Multiple catch Blocks
A single operation can fail in more than one way. Java lets you attach several catch blocks to one try block so you can handle each failure type differently — logging one, retrying another, and presenting a friendly message for a third. Getting this right keeps your error handling both precise and readable.
Why Multiple catch Blocks?
When your code throws different exceptions, a single generic catch clause often forces you to treat every failure the same way. Multiple catch blocks let you react specifically to each situation.
public class FileDemo {
public static void main(String[] args) {
try {
int[] numbers = new int[5];
numbers[10] = 42; // ArrayIndexOutOfBoundsException
int result = 10 / 0; // ArithmeticException (never reached)
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Array index problem: " + e.getMessage());
} catch (ArithmeticException e) {
System.out.println("Math error: " + e.getMessage());
}
}
}
Output:
Array index problem: Index 10 out of bounds for length 5
Java evaluates each catch clause top to bottom and runs only the first one that matches. The remaining blocks are skipped.
The Order of catch Blocks Matters
You must place more specific (child) exception types before more general (parent) types. If a parent class appears first, it swallows every subclass — and the compiler won’t let you put a more specific type after it.
// WRONG — compile error: ArithmeticException has already been caught
try {
int x = 10 / 0;
} catch (Exception e) { // parent first
System.out.println("General");
} catch (ArithmeticException e) { // unreachable — compiler error!
System.out.println("Arithmetic");
}
The correct order is most-specific first:
// CORRECT
try {
int x = 10 / 0;
} catch (ArithmeticException e) { // specific first
System.out.println("Arithmetic: " + e.getMessage());
} catch (Exception e) { // general fallback
System.out.println("Something else: " + e.getMessage());
}
Output:
Arithmetic: / by zero
Warning: Catching
ExceptionorThrowableas the only or first block is a code smell. It hides bugs and makes debugging painful. Always prefer specific types; use a general catch only as a last-resort fallback.
A Realistic Multi-Exception Example
Here is a method that can fail in three distinct ways, each handled differently:
import java.io.*;
public class DataProcessor {
public static void process(String filename, String indexStr) {
try {
// Could throw NumberFormatException
int index = Integer.parseInt(indexStr);
// Could throw FileNotFoundException
FileReader reader = new FileReader(filename);
// Could throw ArrayIndexOutOfBoundsException
String[] data = {"alpha", "beta", "gamma"};
System.out.println("Value: " + data[index]);
} catch (NumberFormatException e) {
System.out.println("Bad index input — not a number: " + e.getMessage());
} catch (FileNotFoundException e) {
System.out.println("File not found: " + filename);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Index out of range: " + e.getMessage());
}
}
public static void main(String[] args) {
process("data.txt", "abc"); // NumberFormatException
process("missing.txt", "1"); // FileNotFoundException
process("any.txt", "99"); // ArrayIndexOutOfBoundsException
}
}
Tip: Each
catchblock receives its ownevariable — a fresh reference to the thrown exception object. You can calle.getMessage(),e.getClass().getName(), ore.printStackTrace()inside any of them.
Multi-Catch: One Block for Multiple Types (Java 7+)
Java 7 introduced the multi-catch syntax — a single catch block that handles several unrelated exception types. Separate them with a pipe (|).
public class MultiCatchDemo {
public static void main(String[] args) {
try {
String s = null;
s.length(); // NullPointerException
} catch (NullPointerException | IllegalArgumentException e) {
// one handler for both types
System.out.println("Caught: " + e.getClass().getSimpleName());
}
}
}
Output:
Caught: NullPointerException
Use multi-catch when you want to apply identical logic to multiple unrelated exception types. It avoids duplicating the same handler code in separate blocks.
Note: The exception variable in a multi-catch block is implicitly
final. You cannot reassigneinside the block. This is intentional — the compiler needs to know the exact type at compile time to verify bytecode.
When to Use Multi-Catch vs Separate Blocks
| Situation | Use |
|---|---|
| Different recovery logic per exception | Separate catch blocks |
| Same logic, unrelated exception types | Multi-catch (|) |
| One exception is a subtype of another | Separate blocks, child first |
| Logging only, then re-throw | Multi-catch is clean |
Catching a Parent Exception Class
You can catch any parent in the hierarchy to handle a whole family of exceptions. This is useful for library boundaries where you want to catch all IOException subtypes:
import java.io.*;
public class IoDemo {
public static void readFile(String path) {
try {
FileReader fr = new FileReader(path);
BufferedReader br = new BufferedReader(fr);
System.out.println(br.readLine());
br.close();
} catch (FileNotFoundException e) {
System.out.println("File missing: " + path);
} catch (IOException e) {
// catches all other IOException subtypes
System.out.println("I/O error: " + e.getMessage());
}
}
}
FileNotFoundException is a subtype of IOException, so it must come first. The second block acts as a catch-all for any other I/O failure.
Under the Hood
When the JVM throws an exception it creates an exception object on the heap and places a reference to it on the operand stack. The JVM then walks the exception table embedded in the class file — a list of [start_pc, end_pc, handler_pc, catch_type] entries generated by the compiler for every catch block.
For multiple catch blocks the compiler emits multiple rows in the exception table for the same try range, each pointing to a different handler PC and a different catch_type constant pool entry. The JVM checks them in order; the first row whose catch_type is assignable from the thrown exception wins. This is why order matters at the source level — it maps directly to row order in the exception table.
Multi-catch (A | B) compiles to a single exception table entry with a handler that checks the type with instanceof internally, producing slightly smaller bytecode than two identical handlers and making it clearer to the reader that the logic is shared. You can inspect the exception table yourself with the javap -c -verbose command.
Tip: Exception handling has virtually zero runtime cost on the happy path (no exception thrown) in modern JVMs — the exception table is consulted only when an exception actually occurs.
Common Mistakes to Avoid
- Catching
Exceptiontoo early — masks bugs likeNullPointerExceptionthat you should fix, not hide. - Empty catch blocks — silently swallowing exceptions makes debugging nearly impossible. At minimum, log the exception.
- Wrong order (parent before child) — a compile-time error when types are in the same hierarchy, but a logic bug if you use unrelated parent types.
- Duplicating handler code — if two separate
catchblocks do the exact same thing, collapse them into a multi-catch.
// Bad: empty catch
try {
riskyOperation();
} catch (IOException e) {
// do nothing — terrible idea
}
// Better: at least log it
try {
riskyOperation();
} catch (IOException e) {
System.err.println("IO failed: " + e.getMessage());
}
Related Topics
- try-catch Block — the foundation that multiple catch blocks build on
- Exception Handling — overview of the full exception system and hierarchy
- Nested try — combining try blocks for fine-grained control
- finally Block — code that always runs whether an exception occurs or not
- Custom Exceptions — defining your own exception types to catch specifically
- throw vs throws — understanding how exceptions are declared and raised