Command Palette

Search for a command to run...

PHASE 7Intermediate ~31 min· topic 2 of 8

Topic 7.2

try, catch and finally

In one line

try marks code that might fail, each catch handles one kind of exception, and finally holds clean-up code that runs whether the try finished normally, returned early, or threw.

Think of it like this

Cooking with an oven. You try to bake a cake. If it burns, you catch that problem and order one from a bakery instead. If the oven door jams, that's a different problem with a different plan. And finally, whatever happened, you switch the oven off. You never leave it on just because the cake burned. Java's try/catch/finally has exactly this shape.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

`try` block
The block of code you want to protect. If something in it throws, the matching handler runs.
`catch` block
A handler for one type of exception (and its subtypes). It receives the exception object in a variable, usually named e.
`finally` block
Code that runs after the try (and any catch) no matter how they ended. Used for clean-up.
Handler
Another name for a catch block: the code that handles an exception.
Clean-up
Giving back something you borrowed from the system, such as closing a file, a network connection or a lock.
Swallowing an exception
Catching an exception and doing nothing useful with it, so the failure disappears without a trace.
Suppressed exception
A second exception that happened while cleaning up after a first one. It's attached to the first so it isn't lost (Topic 7.6).
Abrupt completion
The JLS term for a block ending early because of return, break, continue or a thrown exception, rather than running off its last line.

Step by step

01The shape of the statement

A full try statement has one try block, zero or more catch blocks and an optional finally. At least one catch or the finally is required.

Think of the try as the plan, each catch as a backup plan for one kind of failure, and finally as the 'switch the oven off' step.

Main.javawhole filejava
try {
    int n = Integer.parseInt(input);       // may throw NumberFormatException
    System.out.println(100 / n);           // may throw ArithmeticException
} catch (NumberFormatException e) {
    System.out.println("not a number: " + input);
} catch (ArithmeticException e) {
    System.out.println("can't divide by zero");
} finally {
    System.out.println("done with " + input);
}

02What runs, in which order

Three paths are possible. No exception: the whole try, then finally, then the code after. A matching exception: the try up to the failing line, then the matching catch, then finally, then the code after. A non-matching exception: the try up to the failing line, then finally, then the exception leaves the method.

The flowchart below is worth memorising. Every path goes through finally.

What runs, in which orderdiagram
Rendering diagram…

03Order catch blocks from specific to general

NumberFormatException extends IllegalArgumentException, which extends RuntimeException, which extends Exception. A catch for a parent type catches all its children.

So put the narrowest type first. If you write the broad one first, the narrow one is unreachable and javac stops you. This is a compile error, not a warning: Java refuses to keep dead handlers around.

terminal
$ javac Main.java
── expected output ──
Main.java:6: error: exception RuntimeException has already been caught
} catch (RuntimeException e) {
^
1 error

04finally runs even on return

If the try executes return, the return value is computed and stored in a hidden local variable, then finally runs, then the method returns the stored value. So changing a variable in finally doesn't change an already-computed primitive result.

For an object, the stored value is the reference. finally can't swap the object, but it can still change the object's contents (append to a StringBuilder, add to a list), and the caller sees that change. Same pass-by-value rule as Topic 3.x.

Main.javawhole filejava
static int count() {
    int x = 1;
    try {
        return x;      // 1 is saved in a hidden slot
    } finally {
        x = 99;        // too late: the saved 1 is returned
    }
}

05Never return or throw from finally

A return in finally overrides whatever the try or catch was doing: a pending return value is replaced, and a pending exception is silently discarded. javac only warns about it with -Xlint:finally (finally clause cannot complete normally), so most builds won't even mention it.

An exception thrown from finally (perhaps by a close() call) also replaces the original exception. Your logs show the clean-up failure and the real problem is gone. Wrap risky clean-up in its own try/catch, or better, use try-with-resources.

Never return or throw from finallydiagram
Rendering diagram…

06How javac compiles finally

The JVM has no 'finally' instruction. javac copies the finally block's code onto every exit path: after the normal end of the try, at the end of each catch, and before each return, break or continue that leaves the block. It also adds a catch-all handler (type any in the exception table) that stores the exception, runs another copy of the finally code, and rethrows it.

That's why a large finally block can noticeably grow a method's bytecode. Very old compilers used jsr/ret subroutine instructions instead; the modern class-file verifier (Java 7+ class files) no longer allows them.

07Scope: declare before the try what finally needs

A variable declared inside try { ... } dies at its closing brace. If finally must close a reader, declare it as Reader r = null; before the try, assign it inside, and check for null in finally. That pattern is verbose and easy to get wrong, which is exactly why Topic 7.6 replaces it.

terminal
$ javac Main.java
── expected output ──
Main.java:8: error: cannot find symbol
System.out.println(n);
^
symbol: variable n
location: class Main
1 error

Try it yourself

  1. 1

    Add a path

    In the first example, call attempt(null). Predict what Integer.parseInt(null) throws (hint: it's a NumberFormatException, with a message like Cannot parse null string; the exact wording varies between JDK versions). Predict all lines, then run.

  2. 2

    Remove a catch

    Delete the ArithmeticException catch from the first example and call attempt("0") last. Predict: does finally still print? Does after the try statement print? Then run and read the stack trace.

  3. 3

    Change the returned object

    In appendsAfterReturn, replace b.append("b") with b = new StringBuilder("zzz"). Predict the last line before running. Why is it different from the append version?

Code & diagrams

Three inputs, three paths through try/catch/finally New tab
Sign in to run this example in your browser.

Expected output

-- attempt("4")
try: start
try: parsed 4
try: share 25
finally: always runs
after the try statement
-- attempt("abc")
try: start
catch NFE: For input string: "abc"
finally: always runs
after the try statement
-- attempt("0")
try: start
try: parsed 0
catch AE: / by zero
finally: always runs
after the try statement
finally and return: the surprises New tab

The first line prints before 'returned 1' because the method call runs while the string is being built.

Sign in to run this example in your browser.

Expected output

finally set value to 99
returned 1, value is now 99
returned 2
finally returned normally
object returned: ab
An exception in finally hides the real one New tab

The careful version is what try-with-resources generates for you automatically (Topic 7.6).

Sign in to run this example in your browser.

Expected output

careless: java.lang.IllegalArgumentException: cleanup failed
  suppressed count: 0
careful: java.lang.IllegalStateException: the real problem
  suppressed: java.lang.IllegalArgumentException: cleanup failed
The pre-Java 7 clean-up pattern (fragment)java

Correct but noisy. Topic 7.6 shrinks this to three lines.

BufferedReader reader = null;              // declared outside so finally can see it
try {
    reader = new BufferedReader(new FileReader("notes.txt"));
    System.out.println(reader.readLine());
} catch (IOException e) {
    System.out.println("could not read: " + e.getMessage());
} finally {
    if (reader != null) {                   // the constructor may have failed
        try {
            reader.close();
        } catch (IOException e) {
            // close failed: log it, but don't hide the first error
        }
    }
}

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Put the general catch first

Write catch (Exception e) { ... } catch (RuntimeException e) { ... } after a try block.

terminal
$ javac Main.java
── what you'll see ──
Main.java:6: error: exception RuntimeException has already been caught
} catch (RuntimeException e) {
^
1 error

Break #2

A try with nothing after it

Write try { System.out.println("x"); } with no catch and no finally.

terminal
$ javac Main.java
── what you'll see ──
Main.java:3: error: 'try' without 'catch', 'finally' or resource declarations
try {
^
1 error

Break #3

Catch a checked exception that can't happen

Write try { System.out.println("hi"); } catch (IOException e) { ... }.

terminal
$ javac Main.java
── what you'll see ──
Main.java:6: error: exception IOException is never thrown in body of corresponding try statement
} catch (IOException e) {
^
1 error

Myth vs fact

Myth

finally always runs, no exceptions.

Fact

It runs on every normal and abrupt exit from the try, but not if the JVM stops: System.exit(), a crash, kill -9, or a try that never finishes (infinite loop, deadlock). A daemon thread's finally also doesn't run if the JVM exits around it.

Myth

After a catch, execution goes back into the try.

Fact

The rest of the try is abandoned for good. Execution continues after the entire try statement. If you need a retry, write a loop around the try.

Myth

Changing a variable in finally changes what the method returns.

Fact

The return value was already evaluated and saved. For primitives and reassigned references nothing changes; only mutations to the returned object are visible.

Myth

Several catch blocks can run for one exception.

Fact

Exactly one catch runs: the first whose type matches. If you need the same handling for several types, use a multi-catch (Topic 7.7).

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    JLS 14.20.2 defines the rules precisely: if the try completes abruptly for reason R and finally completes abruptly for reason S, the whole statement completes abruptly for reason S and R is discarded. That one sentence is behind both the 'return in finally' and the 'exception in finally' traps.

  • ▸

    javac inlines the finally body once per exit path plus once in the any handler. A finally block containing a big switch or many statements, in a try with many returns, can push a method past HotSpot's 8000-byte HugeMethodLimit; with DontCompileHugeMethods on (the default) such a method is never JIT-compiled and stays interpreted, quietly costing performance. Extract large clean-up into a method.

  • ▸

    Catching Throwable or Error in general code is dangerous: OutOfMemoryError can strike anywhere, StackOverflowError leaves the thread in a fragile state, and ThreadDeath (deprecated) and the runtime's internal errors expect to propagate. Framework top-level loops do it to log and keep a worker alive; application code almost never should.

  • ▸

    The JIT treats exception paths as cold. A try/catch whose catch path is actually hot (thrown thousands of times a second) may run in a deoptimised state, and implicit exceptions in hot code may lose their stack traces (Topic 7.1's OmitStackTraceInFastThrow).

Remember this

  1. 1

    A try block contains the statements that might throw. It must be followed by at least one catch, a finally, or both (or, from Java 7, it can declare resources: Topic 7.6). Each catch (SomeType e) names an exception type and a variable. When something is thrown inside the try, the rest of the try is skipped and the JVM tests the catch blocks in order, top to bottom, choosing the first one whose type matches (the thrown object is an instanceof that type).

  2. 2

    Because the first match wins, a catch for a subclass must come before a catch for its superclass. If catch (Exception e) came first, a later catch (NumberFormatException e) could never run, and javac rejects it with exception NumberFormatException has already been caught. The compiler also rejects a catch for a checked exception that the try body can't throw (is never thrown in body of corresponding try statement).

  3. 3

    After a catch block finishes, execution continues after the whole try statement. It never returns into the try. If no catch matches, the exception keeps propagating to the caller, exactly as if there were no try at all (but finally still runs on the way out).

  4. 4

    finally runs in every way the try can end: normal completion, an exception that's caught, an exception that isn't caught, return, break and continue. That makes it the place for clean-up: closing files, releasing locks, resetting a flag. The only ways to skip it are drastic: System.exit(), the JVM crashing or being killed, or the try never finishing (an infinite loop or a deadlock).

  5. 5

    finally has two traps. First, a return inside finally replaces the pending return value and even swallows a pending exception, so never return from finally. Second, if finally throws, its new exception replaces the original one, which is lost. try-with-resources (Topic 7.6) was added in Java 7 largely to fix that second problem, by recording the clean-up exception as a suppressed exception instead.

  6. 6

    Variables declared inside the try block are local to it: the catch and finally blocks can't see them. If you need a value in finally, declare the variable before the try. The catch parameter e is local to its own catch block, so every catch can reuse the name e.

Explain it without notes

01

Walk through every way a try/catch/finally statement can be executed.

02

Why must catch blocks for subclasses come before catch blocks for superclasses?

03

What happens when try returns a value and finally also returns or modifies it?

04

When does finally not run?

05

Why is throwing from finally dangerous, and what's the fix?

Practice

01

Write static int safeParse(String s, int fallback) using try/catch, returning the fallback for bad input. Test with "42", "4x" and null.

02

Write a loop that tries to divide 60 by each of {3, 0, 5}, printing the result or skip 0, and prints loop done once at the end. Use finally to print checked <n> after each attempt.

03

Predict, then verify: a method with try { throw new RuntimeException("A"); } catch (RuntimeException e) { throw new RuntimeException("B"); } finally { System.out.println("F"); }. Catch in main and print the message.

Trade-offs

  • ↔

    One big try vs several small ones: one try around a whole method reads cleanly but hides which line failed and handles everything the same way; small trys around the risky calls give precise handling at the cost of more nesting.

  • ↔

    Catch here vs let it propagate: catching close to the failure lets you add context or recover; letting it propagate keeps low-level code simple and lets the layer that knows the business rule decide. Catch only where you can do something meaningful.

  • ↔

    Manual finally vs try-with-resources: finally works for any clean-up (resetting state, unlocking), but closing resources by hand is error-prone; for anything AutoCloseable, try-with-resources is shorter and handles suppressed exceptions correctly.

Done when you can

  • Done when you can trace the exact output of any try/catch/finally for normal, caught and uncaught cases.

  • Done when you order catch blocks from specific to general without thinking.

  • Done when you can explain why return in finally is a bug.

  • Done when you can list the situations where finally does not run.

  • Done when you can protect a primary exception from being replaced by a clean-up failure.