JAVA / EXCEPTIONS
Multiple catch blocks and exception ordering
Order catch clauses correctly, predict which one handles a given throw, and merge duplicate handlers with multi-catch.
What you will learn
- Order catch clauses from most specific type to most general, or javac rejects them.
- Predict which clause runs by finding the first declared type the throw is assignable to.
- Merge identical handlers with catch (A | B e) when A and B are unrelated types.
- Remember a multi-catch variable is final and typed as the common supertype.
Understanding Multiple catch blocks and exception ordering
A try statement may carry any number of catch clauses, and matching walks them in source order, stopping at the first clause whose declared type the thrown object is an instance of. That is first match, not best match: nothing looks for the closest fitting handler the way overload resolution looks for the closest method signature. So which clause handles a NumberFormatException is decided by the order you wrote, not by which clause describes it most precisely.
This makes source order meaningful only between types related by inheritance. Every FileNotFoundException is also an IOException, so a catch (IOException e) written first consumes it and leaves a later catch (FileNotFoundException e) with no input it could ever receive; javac treats that unreachable clause as an error and says the exception has already been caught. Unrelated types such as NumberFormatException and ArrayIndexOutOfBoundsException may appear in either order, because no single throw matches both. The rule "subclasses above superclasses" is simply the arrangement that survives this reachability check.
When two clauses would hold the same body, multi-catch collapses them into catch (A | B e). The alternatives must be unrelated types, since listing a class together with its own supertype would make one alternative redundant in exactly the way the ordering rule forbids. Inside the block, e is implicitly final and its static type is the nearest common supertype of the alternatives, so you can call what they share, such as getMessage() and getClass(), but not a method declared on only one of them.
public class CatchOrder {
static String lookup(String[] table, String rawIndex) {
try {
int i = Integer.parseInt(rawIndex);
return "value: " + table[i].trim();
} catch (NumberFormatException e) {
return "bad number: " + e.getMessage();
} catch (ArrayIndexOutOfBoundsException e) {
return "no such row: " + rawIndex;
} catch (RuntimeException e) {
return "unexpected: " + e.getClass().getSimpleName();
}
}
public static void main(String[] args) {
String[] table = {"alpha", " beta ", null};
System.out.println(lookup(table, "0"));
System.out.println(lookup(table, "1"));
System.out.println(lookup(table, "x1"));
System.out.println(lookup(table, "7"));
System.out.println(lookup(table, "2"));
}
}
Catch clauses are matched in source order by assignability, so a clause is only reachable when no earlier clause already covers its type.
Worked examples
A broad clause first destroys the specific case
Shows that a superclass clause placed first handles its subclasses too, and that the specific clause cannot be added afterwards.
import java.io.FileNotFoundException;
import java.io.IOException;
public class OrderMatters {
static String read(int mode) {
try {
if (mode == 0) throw new FileNotFoundException("config.txt");
throw new IOException("disk error");
} catch (FileNotFoundException e) {
return "missing file: " + e.getMessage();
} catch (IOException e) {
return "io failure: " + e.getMessage();
}
}
static String readWrongOrder(int mode) {
try {
if (mode == 0) throw new FileNotFoundException("config.txt");
throw new IOException("disk error");
} catch (IOException e) {
return "io failure: " + e.getMessage();
}
// catch (FileNotFoundException e) here would not compile
}
public static void main(String[] args) {
System.out.println(read(0));
System.out.println(read(1));
System.out.println(readWrongOrder(0));
}
}
Example explained
Line 1read(0) throws FileNotFoundException and the first clause matches, so the IOException clause is never tested.
Line 2read(1) throws a plain IOException, which is not assignable to FileNotFoundException, so matching moves on to the second clause.
Line 3readWrongOrder(0) throws the same FileNotFoundException, but only the broad clause exists, so the "missing file" distinction is lost at runtime.
Line 4Uncommenting the trailing clause turns this into a compile error rather than a runtime fallback, because IOException already covers it.
Multi-catch for two unrelated failures
Demonstrates one clause serving two sibling exception types and the static type the caught variable gets.
public class MultiCatch {
static int score(String raw) {
int[] table = {10, 20, 30};
try {
return table[Integer.parseInt(raw)];
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
System.out.println("rejected " + raw + " -> " + e.getClass().getSimpleName());
return -1;
}
}
public static void main(String[] args) {
System.out.println(score("2"));
System.out.println(score("nine"));
System.out.println(score("5"));
}
}
Example explained
Line 1score("2") never throws, so the clause is skipped entirely and 30 is returned from inside the try block.
Line 2The two alternatives are siblings under RuntimeException, which is why the compiler accepts them in one clause; IOException | FileNotFoundException would be rejected.
Line 3The static type of e is RuntimeException, the nearest common supertype, so getClass() and getMessage() compile but NumberFormatException-only members would not.
Line 4getClass().getSimpleName() still reports the real runtime class, which is how one clause keeps the two cases distinguishable.
Important notes
Only one clause ever handles a given throw; if that clause's body throws something new, the sibling clauses of the same try never see it and the new exception leaves the method.
The reachability check looks at declared types only, so a leading catch (Exception e) or catch (Throwable t) blocks every later clause even for exceptions the try block could never raise.
Common mistakes
Putting catch (Exception e) first and a specific clause after it: the file will not compile, javac reports "exception ... has already been caught" on the later clause.
Assuming Java picks the most specific handler regardless of order: a broad clause listed first silently swallows its subclasses, so the specific recovery and its message never run.
Writing catch (IOException | FileNotFoundException e): rejected at compile time because the alternatives are related by subclassing, and calling a subclass-only method on a multi-catch variable fails for the same reason its type is widened.
Try it yourself
Change, predict, then run
Write a method that calls Integer.parseInt on a String argument and uses the result to index an int[3], with three clauses printing different lines for NumberFormatException, ArrayIndexOutOfBoundsException and RuntimeException. Then move the RuntimeException clause to the top, compile, and record the exact error javac reports.
Open the Java workspaceCheck your understanding
A try block can throw FileNotFoundException or SocketException, both subclasses of IOException. The clauses are written as catch (IOException e) first, then catch (SocketException e). What happens?
- It fails to compile, because the SocketException clause can never be reached: IOException already covers it
- It compiles, and a SocketException goes to the second clause because that type is more specific
- It compiles, but the second clause runs only if the first clause rethrows the exception
- It compiles with a warning, and a SocketException runs both clauses in order
Show answer
Clauses are tested top to bottom by assignability, so every SocketException already matches the IOException clause, leaving the second clause with no possible input; the language makes that a compile-time error, not a warning. Option 2 assumes best-match selection like method overload resolution, but catch matching is purely positional, and no throw is ever handled by two clauses of the same try.