Java try catch finally: Flow, Rules and Examples

Java try catch finally with runnable Java 25 examples, the order the blocks run, multiple catch blocks, return in finally and when finally is skipped.

Flow chart of try, catch and finally showing the normal path, the caught exception path and the uncaught exception path

In Java, the try-catch-finally statement runs code that may fail in a try block, handles a matching exception in a catch block, and runs the finally block on every exit from try, whether the code completed, threw an exception or returned. The catch and finally parts are each optional, but a try block needs at least one of them or a resource declaration.

We use try and catch to recover from expected failures, such as bad user input or a missing file, instead of letting the thread die. The finally block holds cleanup that must happen in every case, such as releasing a lock.

The following example parses the number of nights for a hotel booking and always releases the room lock.

String input = "three";
String result;
try {
    int nights = Integer.parseInt(input);     // throws NumberFormatException for "three"
    result = "booked " + nights;
} catch (NumberFormatException e) {
    result = "invalid nights: " + input;      // runs because parsing failed
} finally {
    IO.println("room lock released");         // runs in every case
}
String message = result;                      // "invalid nights: three"

Notice that the line after parseInt() never runs, because control jumps straight to the catch block. We cover the syntax rules, the order in which the blocks run, multiple catch blocks, return inside finally, the cases where finally does not run and the switch to try-with-resources.

1. Syntax and Valid Combinations

A try statement starts with the try keyword and a block of code. It continues with zero or more catch clauses, each naming the exception types it handles, and an optional finally block. The JLS 14.20 grammar allows four shapes.

FormCompiles?Typical use
try { } catch (E e) { }YesRecover from a failure and continue
try { } finally { }YesClean up, and let the exception reach the caller
try { } catch (E e) { } finally { }YesRecover and clean up
try (Resource r = …) { }YesClose resources with try-with-resources
try { } aloneNoNone

A lone try block fails with a clear message on JDK 25.

A.java:2: error: 'try' without 'catch', 'finally' or resource declarations

The catch parameter is a local variable that holds the exception object. Its type decides which exceptions the block handles. A catch (IOException e) block also handles FileNotFoundException, because that class extends IOException.

2. How Control Moves Through try, catch and finally

The JVM runs the try block line by line. When a line throws, the rest of the try block is skipped, and the JVM looks for the first catch clause whose type matches the exception. The finally block runs next, whether a catch matched or not.

Flow chart of try, catch and finally showing the normal path, the caught exception path and the uncaught exception path
finally runs on all three paths; an uncaught exception continues to the caller after finally

Say a booking service locks a room while it reads the guest’s input. If the lock is not released on an error path, the room stays blocked for every other guest. That is why the release goes into finally and not at the end of the try block.

The following example is a method that records each step in a list, so we can see the path it took for each input.

static String reserve(String nightsText) {
    List<String> steps = new ArrayList<>();
    try {
        int nights = Integer.parseInt(nightsText);
        steps.add("booked " + nights);
    } catch (NumberFormatException e) {
        steps.add("catch");
    } finally {
        steps.add("finally");
    }
    return String.join(" > ", steps);
}
String valid = reserve("3");               // "booked 3 > finally"
String invalid = reserve("three");         // "catch > finally"

When no catch clause matches, the finally block still runs, and the exception continues up the call stack. The following try-finally program has no catch at all.

// output: try-finally without catch
try {
    IO.println("try: parsing nights");
    int nights = Integer.parseInt("three");
} finally {
    IO.println("finally: room lock released");
}

The output shows finally running before the JVM prints the uncaught exception.

try: parsing nights
finally: room lock released
Exception in thread "main" java.lang.NumberFormatException: For input string: "three"
	at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
	at java.base/java.lang.Integer.parseInt(Integer.java:565)
	at java.base/java.lang.Integer.parseInt(Integer.java:662)
	at Main.main(Main.java:4)

The five paths differ only in which blocks run and where control goes once the statement ends.

What happens in trycatch blockfinally blockAfter the statement
No exceptionSkippedRunsNext statement runs
Exception with a matching catchRunsRunsNext statement runs
Exception without a matching catchSkippedRunsException goes to the caller
catch throws a new exceptionRuns until the throwRunsNew exception goes to the caller
return in trySkippedRuns before the method returnsMethod returns

If no method on the call stack catches the exception, the thread ends and its uncaught exception handler prints the stack trace. Whether checked or unchecked exceptions are involved changes only what the compiler demands, not the order of the blocks.

3. Multiple catch Blocks

A single try block can fail in different ways, and each failure may need a different reaction. We add one catch clause per reaction, and the JVM picks the first one whose type matches. For example, a booking form has a check-in date and a number of nights, and each field gets its own error message.

static String parseBooking(String date, String nights) {
    try {
        LocalDate checkIn = LocalDate.parse(date);
        int count = Integer.parseInt(nights);
        return checkIn + " for " + count + " nights";
    } catch (DateTimeParseException e) {
        return "bad date: " + date;
    } catch (NumberFormatException e) {
        return "bad nights: " + nights;
    }
}
String booking = parseBooking("2026-12-24", "2");   // "2026-12-24 for 2 nights"
String badDate = parseBooking("24/12/2026", "2");   // "bad date: 24/12/2026"
String badCount = parseBooking("2026-12-24", "x");  // "bad nights: x"

3.1. Order the catch Blocks From Specific to General

A catch for a subclass must come before a catch for its superclass, otherwise the specific block can never run and the code does not compile. The compiler reports the unreachable clause.

// does not compile: exception IOException has already been caught
try {
    Files.readString(Path.of("rooms.txt"));
} catch (Exception e) {
    IO.println("general");
} catch (IOException e) {
    IO.println("io");
}

3.2. Catching Several Types in One Block

When two exceptions need the same handling, a multi-catch clause lists them with a vertical bar. Multi-catch exists since Java 7, and its parameter is implicitly final, so we cannot assign a new value to it.

static String validate(String date, String nights) {
    try {
        LocalDate.parse(date);
        Integer.parseInt(nights);
        return "ok";
    } catch (DateTimeParseException | NumberFormatException e) {
        return "invalid input";
    }
}
String check = validate("2026-12-24", "x");     // "invalid input"

The alternatives must not be subclasses of each other. Writing NumberFormatException | IllegalArgumentException fails with “Alternatives in a multi-catch statement cannot be related by subclassing”, because catching IllegalArgumentException alone already covers both.

3.3. Unnamed catch Parameters

Since Java 22, JEP 456 lets us name an unused catch parameter _. The name tells readers that the block ignores the exception object on purpose.

String raw = "two";
int nights;
try {
    nights = Integer.parseInt(raw);
} catch (NumberFormatException _) {
    nights = 1;                               // default stay
}
int stay = nights;                            // 1

4. return Statements and finally

A return inside try does not skip finally. The method computes the return value, runs the finally block, and returns the value it computed before. Changing a local variable in finally does not change a value that has already been chosen.

static int nightsCharged() {
    int nights = 2;
    try {
        return nights;                        // 2 is saved here
    } finally {
        nights = 5;                           // does not change the result
    }
}
int charged = nightsCharged();              // 2

A return inside finally is different, because it replaces the result of try and throws away any exception in flight. The compiler accepts it, prints “finally clause cannot complete normally” only with -Xlint:finally, and IDEs flag it. We keep return, break and continue out of finally blocks.

@SuppressWarnings("finally")
static String confirm() {
    try {
        throw new IllegalStateException("payment declined");
    } finally {
        return "confirmed";                   // hides the exception
    }
}
String status = confirm();                 // "confirmed", the exception is lost

5. When finally Does Not Run

The finally block runs on every normal or exceptional exit from try, but it cannot run if the JVM never gets there. Four cases come up in practice.

  • The code calls System.exit() or Runtime.halt() inside try or catch; the JVM stops without leaving the block.
  • The process is killed, for example with kill -9, or the JVM crashes.
  • The code runs in a daemon thread that is still busy when the last non-daemon thread ends, so the JVM exits.
  • The try block never finishes, such as an endless loop or a call that blocks forever.

A finally block can also stop halfway. If a statement inside it throws, the rest of the block is skipped, and the new exception replaces the one from try.

6. Exceptions Thrown From catch and finally

A catch block may throw an exception of its own, for example to translate a low-level exception into one that the caller understands. The finally block runs first, and the new exception goes to the caller. Passing the original as the cause keeps its stack trace.

static int nightsOrFail(String text) {
    try {
        return Integer.parseInt(text);
    } catch (NumberFormatException e) {
        throw new IllegalArgumentException("nights must be a number", e);
    } finally {
        IO.println("lock released");
    }
}
int parsed = nightsOrFail("x");             // IllegalArgumentException: nights must be a number

An exception from finally is worse, because it replaces the exception from try, and the real cause disappears from the logs. Cleanup code that can throw, such as close(), belongs in try-with-resources, which keeps both exceptions as described in the guide on suppressed exceptions. The throw statement and the throws clause are compared in throw vs throws.

7. Replacing finally With try-with-resources

Most finally blocks in older code close a stream, a reader or a JDBC connection. Since Java 7, try-with-resources closes any AutoCloseable object at the end of the statement, even when the body throws, so the hand-written finally block goes away.

String firstGuest;
try (BufferedReader reader = new BufferedReader(new StringReader("Lokesh\nAlex"))) {
    firstGuest = reader.readLine();
} catch (IOException e) {
    firstGuest = "unknown";
}
String guest = firstGuest;                    // "Lokesh"

A plain finally block is still the right tool for cleanup that is not an AutoCloseable. The classic case is a Lock, whose unlock() call must follow every lock() call.

ReentrantLock roomLock = new ReentrantLock();
roomLock.lock();
try {
    IO.println("updating room 204");
} finally {
    roomLock.unlock();
}
boolean held = roomLock.isLocked();           // false

Notice that lock() is called before try, not inside it. If lock() itself failed inside the try block, finally would call unlock() on a lock we do not hold and throw IllegalMonitorStateException.

8. try catch finally FAQs

Most try-catch-finally questions on Stack Overflow are edge cases around return and missing catch blocks.

8.1. Can we use try without catch in Java?

Yes, if the try block has a finally block or declares resources. A try-finally statement runs its cleanup and lets the exception reach the caller, which has to handle it or declare it.

8.2. Does finally run after return?

Yes. The method evaluates the return value, runs finally, and returns the saved value, as shown in section 4. The block is skipped only when the JVM stops first, for example after System.exit(), or when the try block never finishes.

8.3. Can a try block have more than one finally block?

No. A try statement has at most one finally block, but it can have many catch blocks. Nested try statements each get their own finally.

8.4. Is finally executed when the exception is not caught?

Yes. The finally block runs before the exception leaves the method, so cleanup happens even when the caller or the JVM handles the exception.

8.5. Should we catch Exception or a specific exception?

A specific exception, in most code. Catching Exception also catches runtime exceptions caused by bugs, such as NullPointerException, and treats them like expected failures. A broad catch fits only at the top of a request or a thread, where we log the error and return a generic response.

9. Conclusion

The try block holds code that may fail, catch blocks handle the matching exceptions from the most specific type to the most general, and finally runs on every exit, including a return. The only ways to skip finally are stopping the JVM, killing the process or never leaving the try block.

We never put return in finally, because it hides exceptions. For streams, readers and connections, try-with-resources replaces the finally block and keeps every exception, while locks and other non-closeable cleanup stay in finally.

10. References

Happy Learning !!

Source Code on Github

Leave a Comment

    • Ideally, the catch is used for handling or propagating the exception after adding more context information. We should not write any code in the catch block, which itself can throw exceptions. If such a case happens, then the exception will be propagated to the next catch block, if any, else propagated to JVM and terminates the application.

  1. Agree with Sumit and Ushak.

    In 3.3, the finally block gets executed if exception occurs(or not handled).
    Just ran in IDE.

    • Amit, Yes. I have been wrong and I updated the post after that. Thanks everyone for pointing this out. There is so much for me to learn from you guys. Cheers !!

  2. Hi Lokesh,
    In section, 3.3. try and finally blocks – exception NOT handled, you wrote “the JVM default exception handler handle it. In this case, application is terminated by JVM so finally block is also not executed.” which is wrong. Because, finally block will execute. Please correct me if I am wrong and if I am right please correct it in this page.

      • Yes, Lokesh, You are right but section 3.3 there is no any things which are terminating the JVM. So finally block will be executed. I think your statement is correct but the example is not correct.

        Thanks
        Raveendra

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.