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.
| Form | Compiles? | Typical use |
|---|---|---|
| try { } catch (E e) { } | Yes | Recover from a failure and continue |
| try { } finally { } | Yes | Clean up, and let the exception reach the caller |
| try { } catch (E e) { } finally { } | Yes | Recover and clean up |
| try (Resource r = …) { } | Yes | Close resources with try-with-resources |
| try { } alone | No | None |
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.

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 try | catch block | finally block | After the statement |
|---|---|---|---|
| No exception | Skipped | Runs | Next statement runs |
| Exception with a matching catch | Runs | Runs | Next statement runs |
| Exception without a matching catch | Skipped | Runs | Exception goes to the caller |
| catch throws a new exception | Runs until the throw | Runs | New exception goes to the caller |
| return in try | Skipped | Runs before the method returns | Method 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
- JLS 25, 14.20 The try statement
- Catching and Handling Exceptions (Java Tutorials)
- JEP 456, Unnamed Variables and Patterns
- Runtime.halt (Java SE 25 API)
Happy Learning !!
How to handle the Exception thrown in Catch Block? Can you share that also?
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.
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 !!
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.
Hi Sumit, thanks for sharing your thoughts. If application is terminated by JVM, who is going to run the finally block??
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