try-catch Block
When something goes wrong at runtime — a file is missing, a number is divided by zero, or a network connection drops — Java throws an exception. The try-catch block is your first line of defense: it lets you wrap risky code and respond gracefully when things don’t go as planned, instead of letting the entire program crash.
Why You Need try-catch
Without exception handling, any runtime error terminates your program with a stack trace and leaves the user staring at a confusing error message. The try-catch construct lets you separate the “happy path” code from the “something went wrong” code, keeping both readable and your application alive.
Consider this crash without a try-catch:
public class NoCatch {
public static void main(String[] args) {
int result = 10 / 0; // throws ArithmeticException
System.out.println(result); // never reached
}
}
Output:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at NoCatch.main(NoCatch.java:3)
The JVM prints the stack trace and exits. Wrapping that code in a try-catch changes everything.
Basic Syntax
try {
// code that might throw an exception
} catch (ExceptionType e) {
// code that runs only if the exception occurs
}
- The
tryblock contains the code you want to guard. - The
catchblock specifies which exception type to intercept and what to do about it. - If no exception is thrown, the catch block is skipped entirely.
Your First try-catch
public class BasicTryCatch {
public static void main(String[] args) {
try {
int result = 10 / 0;
System.out.println("Result: " + result);
} catch (ArithmeticException e) {
System.out.println("Caught: " + e.getMessage());
}
System.out.println("Program continues normally.");
}
}
Output:
Caught: / by zero
Program continues normally.
Notice that execution continues after the catch block — the program does not crash. This is the key benefit of exception handling.
The Exception Object
The variable e in catch (ArithmeticException e) is an instance of the exception class. You can call several useful methods on it:
| Method | What it returns |
|---|---|
e.getMessage() | Short human-readable description of the error |
e.toString() | Class name + message |
e.printStackTrace() | Full stack trace printed to stderr |
e.getClass().getName() | Fully qualified exception class name |
e.getCause() | The original cause (for wrapped exceptions) |
try {
String text = null;
int len = text.length(); // NullPointerException
} catch (NullPointerException e) {
System.out.println("Message : " + e.getMessage());
System.out.println("Class : " + e.getClass().getName());
e.printStackTrace(); // useful during debugging
}
Tip: During development,
e.printStackTrace()is your best friend. In production, log the exception using a logging framework like SLF4J/Logback instead of printing directly.
Checked vs Unchecked Exceptions
Java divides exceptions into two categories, and understanding the difference tells you when you are forced to use try-catch.
| Category | Base class | Must handle? | Examples |
|---|---|---|---|
| Checked | Exception (not RuntimeException) | Yes — compiler enforces it | IOException, SQLException, ClassNotFoundException |
| Unchecked | RuntimeException | No — optional but recommended | NullPointerException, ArithmeticException, ArrayIndexOutOfBoundsException |
| Error | Error | Rarely — usually unrecoverable | OutOfMemoryError, StackOverflowError |
For checked exceptions, the compiler will refuse to compile your code unless you either handle them with try-catch or declare them with throws.
import java.io.*;
public class CheckedExample {
public static void main(String[] args) {
try {
FileReader fr = new FileReader("notes.txt"); // checked: FileNotFoundException
int ch = fr.read(); // checked: IOException
System.out.println((char) ch);
fr.close();
} catch (FileNotFoundException e) {
System.out.println("File not found: " + e.getMessage());
} catch (IOException e) {
System.out.println("I/O error: " + e.getMessage());
}
}
}
Note:
FileNotFoundExceptionis a subclass ofIOException. Always catch the more specific exception first — if you putIOExceptionbeforeFileNotFoundException, the compiler will complain that the second catch is unreachable.
Catching Multiple Exceptions (Multi-catch)
Since Java 7 you can catch multiple unrelated exception types in a single catch block using the pipe (|) operator. This avoids duplicating handling code.
public class MultiCatch {
public static void main(String[] args) {
try {
String[] names = {"Alice", "Bob"};
System.out.println(names[5]); // ArrayIndexOutOfBoundsException
int x = Integer.parseInt("abc"); // NumberFormatException
} catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
System.out.println("Caught: " + e.getClass().getSimpleName());
}
}
}
Note: In a multi-catch block, the variable
eis implicitlyfinal— you cannot reassign it inside the block.
For more patterns with multiple catch blocks, see Multiple catch Blocks.
The try-catch-finally Pattern
You can optionally add a finally block that always runs whether or not an exception was thrown — perfect for releasing resources like file handles or database connections.
import java.io.*;
public class TryCatchFinally {
public static void main(String[] args) {
FileReader fr = null;
try {
fr = new FileReader("data.txt");
System.out.println("File opened.");
} catch (FileNotFoundException e) {
System.out.println("File not found.");
} finally {
System.out.println("Finally block always runs.");
if (fr != null) {
try { fr.close(); } catch (IOException ignored) {}
}
}
}
}
Try-with-Resources (Recommended)
Java 7 introduced try-with-resources, which automatically closes any object that implements AutoCloseable. This is the modern, preferred approach and eliminates the boilerplate of the finally-based cleanup above.
import java.io.*;
public class TryWithResources {
public static void main(String[] args) {
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
}
// br is closed automatically here, even if an exception occurs
}
}
Tip: Prefer try-with-resources over manual
finallycleanup whenever you’re working with streams, connections, or anyCloseable— it’s cleaner and less error-prone.
Common Mistakes
1. Catching Exception or Throwable too broadly
// Bad — swallows every possible error, makes debugging hard
try {
riskyOperation();
} catch (Exception e) {
// silently ignoring it is even worse
}
Catch the most specific exception type you can handle. Broad catches hide bugs.
2. Empty catch blocks (swallowing exceptions)
An empty catch block is one of the most dangerous patterns in Java. At minimum, log the error:
} catch (IOException e) {
logger.error("Failed to read config file", e); // always log!
}
3. Catching and re-throwing without wrapping
If you catch an exception just to re-throw it unchanged, you add no value. Either handle it, wrap it in a more meaningful exception, or declare it with throws and let the caller decide.
Under the Hood
When the JVM executes a method, it associates the bytecode with an exception table — a data structure stored in the .class file alongside the method’s bytecode. Each entry in the table records:
- A range of bytecode offsets (the
tryblock’s instructions) - The target offset to jump to (the
catchblock’s first instruction) - The exception class to match
When an exception is thrown, the JVM walks up the call stack looking for a frame whose exception table has a matching entry for the current bytecode offset and exception type. This walk is called stack unwinding. If no matching handler is found after unwinding the entire stack, the JVM invokes the thread’s uncaught exception handler (defaulting to printing the stack trace and terminating).
Because exception table lookups only happen when an exception is actually thrown, the normal (no-exception) execution path carries zero overhead from try-catch. The JIT compiler treats the try block as regular code. This is why it’s safe to use try-catch liberally for truly exceptional conditions — it costs nothing when things go right.
Warning: Do NOT use exceptions for flow control (e.g., catching
NumberFormatExceptionto detect if a string is a number in a tight loop). Throwing and catching an exception involves filling in the stack trace, which is expensive. UseInteger.parseIntwith a prior check or a utility method instead.
You can inspect the exception table of any compiled class with the javap tool:
javap -c -verbose TryCatchFinally.class
Look for the Exception table: section in the output — it lists exactly which bytecode ranges are protected and where control jumps on each exception type.
Related Topics
- Exception Handling — the big picture of Java’s exception hierarchy and philosophy
- Multiple catch Blocks — handling different exceptions from the same try block
- finally Block — code that always runs, paired with try-catch for cleanup
- throw Keyword — how to throw exceptions explicitly from your own code
- Custom Exceptions — creating domain-specific exception classes for cleaner APIs
- throws Keyword — declaring checked exceptions so callers know what to expect