try, catch and finally, blocks
-
Last Updated: October 9, 2024
-
By: javahandson
-
Series
The try, catch and finally blocks are the tools you use to handle exceptions in Java. Learn multiple catch blocks, multi-catch, try-with-resources, and suppressed exceptions with clear beginner examples.
The try, catch and finally blocks are how you actually handle an exception in Java. One block holds the risky work. Another deals with the failure. The third cleans up afterwards.
Think about cooking on a gas stove. You light the burner and cook, which is the try. If the pan catches fire, you grab the lid, which is the catch. Either way, you turn the gas off when you leave, and that is the finally.
That last part trips up beginners. Turning off the gas has to happen whether dinner went well or badly. Files, database connections and network sockets work exactly the same way.
This article is part 2 of our five-part series. Part 1 explained what exceptions are and how Java sorts them. Now we get hands-on with the blocks themselves.
By the end you will write clean handlers, order your catch blocks correctly, and let Java close your files for you. No prior experience with finally required.
We build up one block at a time, then combine them. Here is the plan:
Every example here runs on its own. Copy one into a file, run it, then break it on purpose. Watching a finally block fire while an exception flies past is worth ten paragraphs of theory.
The try block holds the code that might fail. Reading a file. Parsing user text. Dividing by a number you did not choose.
Java watches every line inside that block. The moment one of them throws, Java stops and looks for a handler.
try {
int result = 10 / 0; // this line throws ArithmeticException
System.out.println(result);
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero"); // Output: Cannot divide by zero
}Keep the block small. Wrap only the lines that can genuinely fail, and your catch block stays easy to reason about.
Wrap fifty lines instead, and you lose that clarity. When the catch fires, you have no idea which of those fifty lines caused it.
Here is the single most important rule about try. Java does not skip the bad line and carry on. It abandons the rest of the block entirely.
try {
System.out.println("first");
int[] numbers = {1, 2, 3};
System.out.println(numbers[5]); // throws here
System.out.println("second"); // Java never reaches this
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("caught it");
}
// Output:
// first
// caught itNotice what the output does not contain. The word “second” never appears, because the failure jumped straight past it.
So think carefully about half-finished work. If your try block opens a file and then fails, that file stays open unless something closes it. Section 5 solves exactly that problem.
A bare try block does not compile. Java insists that you follow it with at least one catch block, a finally block, or both.
try {
int result = 10 / 0;
}
// error: 'try' without 'catch', 'finally' or resource declarationsThe reason makes sense once you say it out loud. A try block on its own promises to watch for trouble, then does nothing about it.
One exception exists, and section 7 covers it. A try-with-resources statement can stand with no catch and no finally, because the resource list gives Java something to do.
A variable you declare inside the try block lives only inside those braces. Your catch block cannot see it, and neither can your finally block.
try {
int result = 10 / 2;
} catch (ArithmeticException e) {
System.out.println(result); // error: cannot find symbol
}Beginners hit this constantly. The fix takes one line: declare the variable before the try, then assign it inside.
int result = 0; // declared outside, visible everywhere below
try {
result = 10 / 2;
} catch (ArithmeticException e) {
result = -1;
}
System.out.println(result); // Output: 5A catch block names one exception type and supplies the code that deals with it. When the try block throws a matching type, Java jumps here.
package com.javahandson;
public class CatchBlockExample {
public static void main(String[] args) {
int result = 0;
try {
result = 10 / 0;
System.out.println("Result is : " + result);
} catch (ArithmeticException arithmeticException) {
System.err.println("Arithmetic exception encountered");
}
System.out.println("Program continues");
}
}
// Output:
// Arithmetic exception encountered
// Program continuesLook at that last line. The program did not die. It handled the problem and moved on, which is the whole point of the exercise.
Also notice the type has to match. Catch NullPointerException around that division and the ArithmeticException sails right past you.
That variable in the parentheses holds the real exception object. It carries far more detail than most beginners realise.
getMessage() returns the short description of what went wrong.getClass().getSimpleName() tells you the exact type.printStackTrace() dumps the full call path for debugging.getCause() reveals an underlying exception, when one exists.try {
Integer.parseInt("abc");
} catch (NumberFormatException e) {
System.out.println(e.getClass().getSimpleName()); // Output: NumberFormatException
System.out.println(e.getMessage()); // Output: For input string: "abc"
}Use that detail. A log line saying “something failed” helps nobody at two in the morning.
A catch clause matches the type you name and every subclass below it. This one fact explains almost all catch behaviour.
Catch IOException, for example, and you also catch FileNotFoundException, since it extends IOException.
try {
FileReader reader = new FileReader("missing.txt");
} catch (IOException e) { // FileNotFoundException is a child of IOException
System.out.println("File problem: " + e.getMessage());
}Push that idea to its limit and you get catch (Exception e), which matches nearly everything. Handy, and dangerous, as section 10 explains.
A single try block can have as many catch blocks as you need. Different failures then get different responses.
Java checks them from top to bottom. It runs the first block whose type matches, then skips all the others.
try {
String input = args[0];
int value = Integer.parseInt(input);
System.out.println(100 / value);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Please pass an argument");
} catch (NumberFormatException e) {
System.out.println("That argument was not a number");
} catch (ArithmeticException e) {
System.out.println("Zero is not a valid divisor");
}Three failures, three clear messages. Compare that to one vague line covering all of them, and you can see why separate blocks pay off.
Only one catch block ever runs per trip through the try. Java does not fall through to the next one afterwards.
Because Java takes the first match, the order you write them in decides everything. The rule is short: specific types first, general types last.
Picture a mail sorter. You check for “urgent” before you check for “any letter”. Flip that around and every letter lands in the general pile.
try {
result = 10 / 0;
} catch (ArithmeticException e) { // specific, so it comes first
System.err.println("Arithmetic exception encountered");
} catch (Exception e) { // general, so it comes last
System.err.println("Some other exception encountered");
}
// Output: Arithmetic exception encounteredArithmeticException matched, so Java ran that block and ignored the one below. The general catch stays there as a safety net for anything else.
Put the general type first and your code does not compile at all. Java refuses, rather than letting you ship a block that can never run.
try {
result = 10 / 0;
} catch (Exception e) { // too broad, and it comes first
System.err.println("Some exception encountered");
} catch (ArithmeticException e) { // unreachable
System.err.println("Arithmetic exception encountered");
}
// error: exception ArithmeticException has already been caughtRead that compiler message closely. ArithmeticException extends Exception, so the first block already covers it. The second block could never execute.
This is a friendly error, honestly. The compiler catches your ordering slip long before a user ever could.
Sometimes two different failures deserve the very same response. Copying the block twice feels wrong, and Java 7 gave us a better option.
List the types separated by a pipe character, and one block handles them all.
package com.javahandson;
public class MultiCatchExample {
public static void main(String[] args) {
String text = "javahandson";
try {
System.out.println(text.toUpperCase());
int value = Integer.parseInt(text); // throws NumberFormatException
System.out.println(100 / value);
} catch (NumberFormatException | ArithmeticException exception) {
System.out.println("Bad value: " + exception.getMessage());
}
}
}
// Output:
// JAVAHANDSON
// Bad value: For input string: "javahandson"Before Java 7 you needed two near-identical blocks for this. The pipe removes that duplication without hiding anything.
Multi-catch comes with two restrictions, and both make sense once you see the reason.
First, the types must not sit in a parent-child relationship. Writing catch (IOException | FileNotFoundException e) fails to compile, because IOException already covers its child.
Second, the variable behaves as final. You cannot reassign it inside the block, since Java cannot know which of the listed types actually arrived.
// error: alternatives related by subclassing
catch (IOException | FileNotFoundException e) { }
// error: multi-catch parameter may not be assigned
catch (NumberFormatException | ArithmeticException e) {
e = new ArithmeticException();
}One more judgement call. Use multi-catch only when the recovery genuinely matches for every listed type. Force unrelated failures together and your messages turn vague again.
The finally block holds cleanup code. Java runs it after the try block, and after any catch block, no matter how things went.
Why do we need a whole block for that? Because an exception skips the rest of your try block, and your cleanup line usually sits right there in the skipped part.
try {
// risky work
} catch (ExceptionType e) {
// handle the failure
} finally {
// cleanup that always happens
}Typical work for a finally block looks like this:
See the pattern? Every item undoes something the try block started. Skip that undo and you leak a resource.
Let us prove the promise with two runs of the same program. First, a failure.
package com.javahandson;
public class FinallyBlockExample {
public static void main(String[] args) {
try {
int result = 10 / 0;
System.out.println(result);
} catch (ArithmeticException arithmeticException) {
System.err.println("Arithmetic exception encountered");
} finally {
System.out.println("Finally block ran");
}
}
}
// Output:
// Arithmetic exception encountered
// Finally block ranNow change the divisor to 2 and run it again. The catch block sits idle this time, yet the last line still appears.
try {
int result = 10 / 2;
System.out.println(result);
} catch (ArithmeticException arithmeticException) {
System.err.println("Arithmetic exception encountered");
} finally {
System.out.println("Finally block ran");
}
// Output:
// 5
// Finally block ranSuccess or failure, the finally block runs. That reliability is the entire reason it exists.
“Always” has a few honest exceptions, and interviewers love asking about them. Java skips finally only when the whole program stops or never gets there.
System.exit(), which shuts the JVM down on the spot.Here is the System.exit case in code, since it is the one you can actually trigger.
package com.javahandson;
public class ExitSkipsFinally {
public static void main(String[] args) {
try {
int result = 10 / 0;
} catch (ArithmeticException arithmeticException) {
System.out.println("Arithmetic exception encountered");
System.exit(0); // JVM stops right here
} finally {
System.out.println("You will never see this");
}
}
}
// Output: Arithmetic exception encounteredNotice how the finally line vanished from the output. System.exit does not unwind anything. It ends the JVM immediately, so nothing downstream gets a turn.
What if your catch block returns a value? Surely that ends the method right away? Not quite.
Java runs the finally block first, then hands the return value back to the caller. The return waits.
package com.javahandson;
public class FinallyWithReturn {
public static void main(String[] args) {
System.out.println("Result is : " + getResult());
}
private static int getResult() {
try {
return 10 / 0;
} catch (ArithmeticException arithmeticException) {
System.out.println("Arithmetic exception encountered");
return 10; // this value waits
} finally {
System.out.println("Finally block ran");
}
}
}
// Output:
// Arithmetic exception encountered
// Finally block ran
// Result is : 10Trace the order in that output. The catch prints, the finally prints, and only then does the caller receive 10.
This behaviour is precisely why finally can be trusted for cleanup. A return statement cannot sneak past it.
A try block needs a catch or a finally, but not necessarily both. So try plus finally with no catch at all compiles fine.
When would you want that? Sometimes you have no useful way to handle a failure here, so you let it travel up to a caller who does.
Even so, you still opened a file. Somebody has to close it, and the finally block does that job on the way out.
This method reads a file and declares IOException, so it never catches anything itself. The finally block still guarantees the close.
package com.javahandson;
import java.io.FileReader;
import java.io.IOException;
public class TryFinallyExample {
public static void main(String[] args) throws IOException {
FileReader fileReader = null;
try {
fileReader = new FileReader("file.txt");
int data;
while ((data = fileReader.read()) != -1) {
System.out.print((char) data);
}
} finally {
if (fileReader != null) {
fileReader.close();
System.out.println("\nFile reader closed.");
}
}
}
}
// Output:
// Hello, How are you
// File reader closed.Count the ceremony in that finally block. A null check, a close call, and a nested problem if close itself fails. Section 7 removes all of it.
Look again at the previous example. To close one file safely you wrote a null check plus a close call, and a strict version needs another try around the close.
Now imagine two resources. The nesting doubles, and one forgotten close leaks a file handle for the life of the process.
Java 7 fixed this properly. Declare the resource in the try statement itself, and Java closes it for you.
Put the resource in parentheses right after the word try. Everything else stays familiar.
package com.javahandson;
import java.io.FileReader;
import java.io.IOException;
public class TryWithResourcesExample {
public static void main(String[] args) throws IOException {
try (FileReader fileReader = new FileReader("file.txt")) {
int data;
while ((data = fileReader.read()) != -1) {
System.out.print((char) data);
}
} // Java closes fileReader here, automatically
}
}
// Output: Hello, How are youCompare the two versions side by side. The null check disappeared. So did the close call, and so did the nested try inside finally.
You can also declare several resources, separated by semicolons. Java closes every one of them.
try (FileReader reader = new FileReader("in.txt");
FileWriter writer = new FileWriter("out.txt")) {
int data;
while ((data = reader.read()) != -1) {
writer.write(data);
}
}Remember section 2.3, where a bare try refused to compile? A try-with-resources block is the exception. Since the resource list already gives Java work to do, you may write it with no catch and no finally.
Only a class implementing AutoCloseable may appear in that list. The interface asks for one method, close(), and Java calls it for you.
Most classes you already use qualify. Streams, readers, writers, sockets, JDBC connections and statements all implement it.
AutoCloseable declares close(), which may throw any Exception.Closeable extends it and narrows the throw to IOException.Since Java 9 you can also list a variable you declared earlier, as long as it stays final or effectively final. That helps when another method hands you the resource.
FileReader reader = new FileReader("file.txt"); // effectively final
try (reader) { // legal since Java 9
System.out.println(reader.read());
}Two details here catch people out in interviews, so let us be precise.
Java closes resources in the reverse order of declaration. The last one you opened closes first, exactly like unstacking plates.
Timing matters even more. Java closes the resources before it runs your catch or finally block, so by the time your handler executes, the file has already gone.
try (Resource first = new Resource("A");
Resource second = new Resource("B")) {
throw new RuntimeException("boom");
} catch (RuntimeException e) {
System.out.println("catch runs last: " + e.getMessage());
}
// Output:
// closing B
// closing A
// catch runs last: boomNow a subtle case. What if your try block throws, and then close() throws as well? Two exceptions, one method. Which one wins?
The old try-finally answer was terrible. The close exception replaced the original, and your real problem disappeared without trace.
try-with-resources handles it far better. The exception from your try block wins, and Java attaches the close exception to it as a suppressed exception.
try (Resource r = new Resource("A")) { // close() also throws
throw new IllegalStateException("primary failure");
} catch (Exception e) {
System.out.println("primary: " + e.getMessage());
for (Throwable suppressed : e.getSuppressed()) {
System.out.println("suppressed: " + suppressed.getMessage());
}
}
// Output:
// primary: primary failure
// suppressed: failed to close ANothing gets lost. You see the real cause first, with the cleanup failure recorded alongside it. A stack trace prints these under a “Suppressed:” line.
You can place a try block inside another one. Java then works from the inside out.
When the inner block throws, Java checks the inner catch blocks first. Only if none of them match does the exception move to the outer try.
try {
System.out.println("outer starts");
try {
int[] numbers = {1, 2, 3};
System.out.println(numbers[5]);
} catch (ArithmeticException e) {
System.out.println("inner catch: wrong type, so it does not match");
}
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("outer catch handled it");
}
// Output:
// outer starts
// outer catch handled itThe inner catch named the wrong type, so Java passed the problem outward. The outer block matched and dealt with it.
Nesting helps when one small step inside a larger operation needs its own recovery. Parsing one row of a file, for instance, while the outer block guards the file itself.
It also shows up in old cleanup code, where closing a resource inside finally needed its own try. You saw that shape in section 6, and try-with-resources retires it.
Use nesting sparingly. Two levels read fine, but three levels of braces usually means a separate method would read better.
Both approaches close your resources. Here is how they compare:
| Aspect | try-finally | try-with-resources |
|---|---|---|
| Available since | Java 1.0 | Java 7 |
| Who closes the resource | You do, by hand | Java does it for you |
| Null check needed | Yes | No |
| If close() throws | It replaces your real exception | Java records it as suppressed |
| Two resources | Nested blocks | One list, separated by semicolons |
| Requires AutoCloseable | No | Yes |
The verdict is easy. Use try-with-resources for anything closeable, and keep try-finally for cleanup that has nothing to close, such as releasing a lock or stopping a timer.
This one is genuinely nasty. A return inside finally throws away everything the method was about to do, including a pending exception.
private static int broken() {
try {
throw new IllegalStateException("real problem");
} finally {
return 42; // the exception silently disappears
}
}
// Output: 42, and nobody ever hears about the exceptionYour caller receives 42 and assumes all went well. The real failure vanished without a stack trace or a log line.
The fix is simple. Never write return inside a finally block, and most compilers warn you about it anyway.
An exception thrown from finally does the same damage. It replaces whatever the try block was already reporting.
try {
throw new IllegalStateException("real problem");
} finally {
connection.close(); // if this throws, the real problem is lost
}Keep finally blocks boring. Guard anything risky in there with its own try-catch, or better still, switch to try-with-resources and let Java suppress it properly.
In manual cleanup, the resource variable might still hold null. That happens when the constructor itself failed.
FileReader reader = null;
try {
reader = new FileReader("missing.txt"); // throws, so reader stays null
} finally {
reader.close(); // NullPointerException, hiding the real error
}So your log now reports a NullPointerException instead of the missing file. Check for null first, or skip the whole trap with try-with-resources.
A giant try block feels safe, but it blurs everything. When the catch fires, which of those forty lines actually broke?
Worse, one catch clause now covers unrelated failures. Your recovery code cannot possibly suit all of them.
Wrap the smallest region that can fail. Your handler then knows exactly what it is answering.
A finally block is for cleanup, not for business logic. Saving records or sending emails from there causes odd behaviour, because that code runs even during a failure.
Ask yourself one question. Should this line run when everything just went wrong? If the answer is no, it belongs in the try block instead.
Let us combine every block in one small program. We want to read numbers from a file, one per line, and total them.
Three things can go wrong here. The file might not exist. A line might hold a word instead of a number. And whatever happens, the file must close.
Each block earns its place. try-with-resources handles the file, two catch blocks handle the two failures, and finally reports the outcome.
package com.javahandson;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class NumberTotal {
public static void main(String[] args) {
System.out.println("Total: " + total("numbers.txt"));
}
static int total(String path) {
int sum = 0;
int lineCount = 0;
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
String line;
while ((line = reader.readLine()) != null) {
lineCount++;
try {
sum += Integer.parseInt(line.trim());
} catch (NumberFormatException e) {
System.out.println("Line " + lineCount + " is not a number: " + line);
}
}
} catch (IOException e) {
System.out.println("Could not read " + path + ": " + e.getMessage());
return -1;
} finally {
System.out.println("Finished after " + lineCount + " lines");
}
return sum;
}
}
// numbers.txt contains: 10, 20, oops, 30
// Output:
// Line 3 is not a number: oops
// Finished after 4 lines
// Total: 60Follow the flow line by line. The outer try opens the file as a resource, so Java closes it whatever happens next.
Inside the loop, a small nested try guards one parse. The word “oops” throws NumberFormatException, the inner catch prints a note, and the loop carries straight on to line 4.
That nesting matters. Without it, one bad line would abandon the entire file. With it, we lose one value and keep the other three.
Now delete numbers.txt and run it again. The FileReader constructor throws, the IOException catch prints a message, and the method returns -1.
// With numbers.txt deleted // Output: // Could not read numbers.txt: numbers.txt (No such file or directory) // Finished after 0 lines // Total: -1
Look closely at that second run. The finally line still printed, even though the catch block returned -1. Section 5.4 promised exactly that, and here it is in practice.
A: The try block holds the code that might fail. A catch block handles a specific exception type when that failure happens. The finally block holds cleanup code that Java runs either way, so resources close whether the try block succeeded or threw.
A: Yes, as long as a finally block follows it. A try needs at least one catch or a finally, otherwise the code does not compile. A try-with-resources statement is the one form that can stand alone with neither.
A: Java tests catch blocks from top to bottom and runs the first matching type. A broad type such as Exception placed first would match everything, leaving the later blocks unreachable. The compiler rejects that with an “already been caught” error.
A: Almost always, including when the try or catch block returns a value. Java skips it only if the program never leaves the try block or never gets that far: a System.exit() call, a JVM crash, the operating system killing the process, or an infinite loop or deadlock inside try.
A: The return in finally wins, and it discards the earlier one. Worse, it also discards any pending exception, so a real failure disappears silently. Never put a return inside a finally block.
A: try-with-resources arrived in Java 7. You declare a resource in parentheses after the try keyword, and Java calls close() on it automatically when the block ends. It removes the null check and the manual close that try-finally needs, and any class implementing AutoCloseable qualifies.
A: In reverse order of declaration, so the last resource you opened closes first. Java also closes every resource before it runs your catch or finally block, which means the resources have already gone by the time your handler executes.
A: When your try block throws and close() throws as well, try-with-resources keeps the exception from the try block as the primary one and attaches the close failure to it. You read those extras with getSuppressed(), and a stack trace prints them under a “Suppressed:” line. Plain try-finally loses the original instead.
A: Yes. Java 7 added multi-catch, where you separate the types with a pipe, as in catch (IOException | SQLException e). Two rules apply: the listed types must not be parent and child of each other, and the caught variable behaves as final, so you cannot reassign it.
A: No. A variable declared inside the try braces goes out of scope the moment that block ends, so the catch and finally blocks cannot see it. Declare it before the try statement and assign it inside if you need it later.
Let us wrap up what we covered. The try block holds risky code, and Java abandons the rest of that block the moment a line throws.
A catch block handles one type plus all its subclasses. List several of them from specific to general, or combine matching cases with the multi-catch pipe from Java 7.
The finally block runs on both paths, success and failure, and even beats a return statement. Only a System.exit call, a crash, or a block that never ends can skip it.
For anything closeable, reach for try-with-resources instead of manual cleanup. Java closes your resources in reverse order, before the catch runs, and records any close failure as a suppressed exception.
Keep try blocks small, keep finally blocks boring, and never return from finally. Part 3 of this series moves on to raising exceptions yourself with throw and throws.