Java Catch Ordering
›Short versionOpen if you only have a minute.
- Java tests catch clauses from top to bottom and runs the first one whose type accepts the thrown object. It does not pick the most specific clause in the list.
- A parent type above a child type handles the child. The compiler then rejects the child clause as already caught.
- Sibling types may sit in either order. Choose the order a reader of this method should meet.
- Multi-catch alternatives must be disjoint. A parent and a child in one clause cannot compile, because the child alternative would never matter.
- A catch of a checked type the try cannot throw is rejected. A catch of Exception or of an unchecked type is allowed even when the body never names it.
Order is the whole algorithm. A thrown object is offered to each clause in turn, and the handles it. The compiler's job is to reject a later clause that this rule has already made dead, and to reject a multi-catch whose alternatives overlap in the same way.
The first clause that fits
Opening the path throws FileNotFoundException when it does not name a file the reader can open. Reading can throw a broader IOException. That class extends Exception. Any of the three clauses below could accept some failure from this statement. The first one in the source whose type fits is the one that runs.
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
static void open(String path) {
try (FileReader reader = new FileReader(path)) {
reader.read();
} catch (FileNotFoundException e) {
System.out.println("That path is not a file");
} catch (IOException e) {
System.out.println("Some other I/O failure");
} catch (Exception e) {
System.out.println("Not an I/O failure");
}
}A missing file prints the first sentence. An IOException from read that is not a FileNotFoundException skips the first clause, because the object is not that type, and prints the second sentence. A runtime exception from the body is still an Exception, so it reaches the last clause. That clause is wide on purpose, and it is last so it does not steal the two cases above it.
A child clause under its parent is dead
Reverse the first two clauses and the program does not compile. catch (IOException e) already accepts every FileNotFoundException. The later clause is .
try {
new FileReader(path);
} catch (IOException e) {
System.out.println("I/O problem");
} catch (FileNotFoundException e) {
// exception FileNotFoundException has already been caught
System.out.println("Missing file");
}The check is static. The compiler does not care that today the constructor only throws the subclass. It cares that the type of the earlier parameter is a supertype of the later one, so there is no object that could pass the first test and still match the second. The same error appears if the two clauses name the exact same type.
have no such test to fail. NumberFormatException and ArithmeticException can be written in either order. Each rejects the other's objects. Order them by what you want a reader to see first, which is usually the check the method performs first.
The same rule inside one clause
Multi-catch is one clause with several alternatives. The alternatives are tested as a group, and they must be disjoint. A parent and a child in the same clause would make the child alternative redundant, so the compiler rejects it: alternatives in a multi-catch statement cannot be related by subclassing.
try {
new FileReader(path);
} catch (FileNotFoundException | IOException e) {
// does not compile: FileNotFoundException is a subclass of IOException
}NumberFormatException | ArithmeticException is legal. Those types share no inheritance edge. The parameter is implicitly final, and its static type is their least upper bound. Separate clauses remain the right tool when a missing file should write a default and every other IOException should abort. Multi-catch cannot express that split, both because the types are related and because one block has one body.
A clause the body can never throw
Order is one compile-time check. is the other. If the try block cannot throw a checked exception assignable to the clause, the clause is rejected. parseInt throws an unchecked NumberFormatException. It does not throw IOException.
try {
Integer.parseInt(raw);
} catch (IOException e) {
// exception IOException is never thrown in body of corresponding try statement
}Replace IOException with Exception, with RuntimeException, or with NumberFormatException, and the clause compiles. The compiler tracks checked exceptions out of the body. It does not track unchecked ones, so a catch of an unchecked type is allowed whether or not the body mentions it. A checked catch has to correspond to a call, a throw, or a declaration that can actually produce that type.
Earlier clauses narrow what a later one can still see. After catch (FileNotFoundException e), a following catch (IOException e) is still useful for the other checked I/O failures the body might throw. After catch (IOException e), there is no checked I/O failure left, and a child clause is the "already been caught" error rather than the "never thrown" error.
catch (Exception e) comes first, and catch (IOException e) comes second. The try block calls a method that declares throws IOException. Which clause is rejected, and which check rejects it?
The IOException clause is rejected, because Exception is a supertype of it. Every IOException has already been caught by the first clause. That is the unreachable-catch check, the "already been caught" error. The reachability check would be the other message, and it would apply if the body could not throw IOException at all and you caught that type with no wider clause above it. Here the body can throw it. The order is what makes the second clause dead.