The basic try/catch shape
Student can produce a try/catch block that handles a specific checked exception with an informative user message, identify which statements in the try block are skipped when an exception fires, and explain how execution continues after the catch body completes.
A try/catch block lets a program respond to an exception instead of letting it crash the program. The syntax (JLS §14.20):
try {
// code that might throw
} catch (FileNotFoundException e) {
// recovery code; e is the exception object
}
The semantics:
1. The try block runs normally. If no exception is thrown, the block finishes and execution continues with whatever follows the catch blocks. 2. If an exception is thrown inside try, control jumps to the matching catch. "Matching" means the exception's class is the catch's declared type or a subclass of it. The try block does not finish; remaining statements after the throw are skipped. 3. After the catch body runs, execution continues with the statement after the catch. The exception is consumed by the catch; it does not propagate further up the call stack. 4. If no catch matches, the exception propagates. It bubbles up the call stack until either a caller's catch matches it or the JVM's default handler prints a stack trace and exits.
public static void main(final String[] args) {
final Scanner kbd = new Scanner(System.in);
System.out.print("Filename: ");
final String name = kbd.nextLine();
try {
final Scanner sc = new Scanner(new File(name));
// ... read the file ...
sc.close();
} catch (FileNotFoundException e) {
System.out.println("Could not open " + name + ": " + e.getMessage());
}
System.out.println("Program continues here regardless.");
}
If the file exists, the try body runs to completion and the catch is skipped. If the file is missing, the try body stops at new Scanner(new File(name)), the catch body runs, and execution continues at the final println.
What goes inside the catch block
The CSCD 210 lab style for catch bodies:
- A clear user-facing message:
System.out.println("Could not open " + name + ": " + e.getMessage()); - Optionally a
return;orSystem.exit(1);if the program cannot continue without the resource. - Never an empty body. The
eparameter must be used (printed, logged, included in the user message). An empty catch swallows the exception silently and is a documented anti-pattern (Bloch Effective Java Item 77).
The e parameter: what is in it
The catch parameter is the exception object. Useful methods on Throwable:
e.getMessage(): the human-readable detail (e.g., "data.txt (No such file or directory)").e.toString(): class name plus message ("java.io.FileNotFoundException: data.txt").e.printStackTrace(): print the full stack trace toSystem.err.e.getClass().getSimpleName(): short class name ("FileNotFoundException") when you do not want the full package.
For CSCD 210 lab style, e.getMessage() is usually enough. The full stack trace is for debugging; user-facing programs hide it.
In other languages
- Python:
try: ... except FileNotFoundError as e: ...: same shape, different keywords. - C++:
try { ... } catch (std::exception& e) { ... }: same shape, references not values. - JavaScript:
try { ... } catch (e) { ... }: note no declared exception type;ecan be anything. - Rust: no exceptions;
Result<T, E>plus pattern matching. The error is part of the return type.
Multiple catch blocks: one per exception type
Student can produce a try/catch block with multiple catches, one per distinct exception type and recovery, predict which catch will execute given a specific thrown exception, and use the multi-catch shorthand catch (A | B e) when the recovery code is identical.
A try statement may have more than one catch block. Each catch is matched against the thrown exception's class in source order; the first matching catch is executed.
try {
final Scanner sc = new Scanner(new File(name)); // can throw FileNotFoundException
final int n = sc.nextInt(); // can throw InputMismatchException
final int avg = total / n; // can throw ArithmeticException if n == 0
} catch (FileNotFoundException e) {
System.out.println("Cannot open file: " + e.getMessage());
} catch (InputMismatchException e) {
System.out.println("File contains non-integer data: " + e.getMessage());
} catch (ArithmeticException e) {
System.out.println("File contained zero, would divide by it.");
}
Three properties of the multi-catch shape:
1. Each catch has its own scope. The variable e inside the first catch is distinct from e inside the second. They can have different declared types and different recovery code. The convention is to name them all e since their scopes do not overlap. 2. Order matters when types are related. If two catches declare types in an inheritance relationship (one is a subclass of the other), the more specific must come first. The catch-ordering rule covers this in detail. 3. At most one catch runs per exception. Whichever catch matches first handles the exception; the others are skipped for that throw. The execution then continues after the last catch, not at the next catch.
The multi-catch shorthand (one catch, multiple types)
Java 7 added a syntax for catching multiple types in one catch block when the recovery code is identical:
try {
// ...
} catch (FileNotFoundException | InputMismatchException e) {
System.out.println("File problem: " + e.getMessage());
}
The | separates types; the variable e has the common supertype of the listed exceptions (here Exception). This is shorter when the recovery is the same but should not be used to paper over the fact that two failure modes have genuinely different remedies.
When to use multiple catches vs one general one
The narrow rule: catch what the recovery code genuinely differs for. If "file missing" should prompt the user to re-enter the filename, and "bad data in the file" should report the line number and ask the user to fix the file, the two recoveries are different and want separate catches. If both failure modes are handled the same ("tell the user, log the error, give up"), one catch suffices.
What you do not want: a chain of catches whose bodies all do the same thing. That is a code smell: the right form is catch (Exception e) (with the narrower-types caveat from the catch-ordering rule), or the multi-catch shorthand above.
In other languages
- Python:
try: ... except FileNotFoundError: ... except ValueError: ...: same shape;exceptinstead ofcatch. - C++:
try { ... } catch (FileNotFoundException& e) { ... } catch (ParseException& e) { ... }: same. - JavaScript: only one
catchpertry(no multiple catches). The catch body must dispatch oninstanceofto handle different types.
Catch order: most specific first
Student can order multiple catch blocks so that more-specific exception types appear before more-general ones, predict the compile-time error when the order is reversed, and identify when a chain of catches is correct versus when it can be merged.
When a try statement has multiple catches whose declared types are in an inheritance relationship, the more specific type must come first. Java's compiler enforces this; the rule is not a style preference.
// CORRECT
try {
new Scanner(new File(name));
} catch (FileNotFoundException e) { // specific child first
System.out.println("missing file");
} catch (IOException e) { // general parent second
System.out.println("other I/O issue");
}
// COMPILE ERROR
try {
new Scanner(new File(name));
} catch (IOException e) { // parent first
System.out.println("other I/O issue");
} catch (FileNotFoundException e) { // child second: unreachable!
System.out.println("missing file");
}
// error: exception java.io.FileNotFoundException has already been caught
The rule follows from the type hierarchy: every FileNotFoundException is also an IOException. If catch (IOException e) runs first, it matches every thrown FileNotFoundException and every thrown sibling subclass of IOException. The second catch could never fire, so it is dead code, and the compiler rejects it.
Why this matches the inheritance graph
The rule is the type-system manifestation of the most-specific catch should run. Without the rule, callers could be misled: the broader catch would silently consume failures the narrower catch was designed for. The CSCD 210 lab style favors this discipline:
- Open the most likely failure mode's catch first.
- Add a catch for the general parent as a fallback only when the parent has subclasses other than the specific one(s).
- Never write
catch (Exception e)andcatch (Throwable e)together: they are related by inheritance, and the order rule forbids the wrong one.
Three patterns by purpose
Specific-only: one catch, narrow type. Used when the only failure mode is the named one.
try { ... }
catch (FileNotFoundException e) { ... }
Specific-plus-general fallback: two catches, specific child first, general parent second. Used when you expect a specific failure but still need to handle the sibling types.
try { ... }
catch (FileNotFoundException e) { /* missing file */ }
catch (IOException e) { /* permission denied, disk full, etc. */ }
Combined siblings: multi-catch on parallel siblings, no inheritance relationship between them.
try { ... }
catch (FileNotFoundException | InputMismatchException e) { ... }
Compiler hint: "exception ... has already been caught"
The compile error message names exactly the unreachable catch. The fix is always to either (a) reorder the catches with specific-first, or (b) remove the redundant catch. The IDE quick-fix usually offers both options.
In other languages
- Python: same rule (
except ChildError: ... except ParentError: ...). The interpreter raisesSyntaxError: default 'except:' must be lastif a bareexcept:appears before a typed one. - C++: same rule; out-of-order produces a warning, not an error (and the later catch never fires).
- C#: same rule; the compiler enforces it like Java does.