Java try-catch-finally — Handling Exceptions
The try-catch-finally statement is Java’s primary mechanism for handling exceptions. It lets you attempt risky code, catch failures, and clean up resources — regardless of what happens.
Basic Syntax
try {
// Code that might throw an exception
} catch (ExceptionType e) {
// Handle the exception
} finally {
// Always executes — cleanup code
}
The try Block
The try block wraps code that might throw an exception:
try {
int result = 10 / 0; // throws ArithmeticException
System.out.println(result); // never reached
}
Key rules:
- Variables declared inside
tryare scoped to that block — not visible incatchorfinally. - If an exception occurs, execution jumps immediately to the matching
catchblock. - Code after the throwing line inside
tryis skipped.
The catch Block
Catches a specific exception type and provides a reference to the exception object:
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("Error: " + e.getMessage());
// Error: / by zero
}
What You Can Do with the Exception Object
catch (ArithmeticException e) {
e.getMessage() // "/ by zero"
e.getClass().getName() // "java.lang.ArithmeticException"
e.printStackTrace() // full stack trace to stderr
e.getStackTrace() // StackTraceElement[] array
}
The finally Block
finally always executes — whether an exception occurred or not, even after a return:
public static int divide(int a, int b) {
try {
return a / b;
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero");
return -1;
} finally {
System.out.println("Finally block runs!");
}
}
divide(10, 2);
// Output:
// Finally block runs!
// Returns: 5
divide(10, 0);
// Output:
// Cannot divide by zero
// Finally block runs!
// Returns: -1
When Does finally Run?
| Scenario | finally Runs? |
|---|---|
| No exception | ✅ |
| Exception caught | ✅ |
| Exception NOT caught | ✅ (before propagation) |
return in try |
✅ (before return) |
return in catch |
✅ (before return) |
System.exit() |
❌ |
| JVM crash | ❌ |
Infinite loop in try |
❌ (never reaches finally) |
finally for Resource Cleanup
The classic use case — closing resources:
FileReader reader = null;
try {
reader = new FileReader("data.txt");
// read from file
} catch (IOException e) {
System.out.println("Error reading file: " + e.getMessage());
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
System.out.println("Error closing file");
}
}
}
This is verbose and error-prone. Java 7’s try-with-resources (covered later) is the modern replacement.
Multiple catch Blocks
Handle different exception types differently:
try {
String[] arr = {"10", "abc", null};
int index = 1;
int value = Integer.parseInt(arr[index]);
System.out.println(value);
} catch (NumberFormatException e) {
System.out.println("Not a valid number: " + e.getMessage());
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Index out of range: " + e.getMessage());
} catch (NullPointerException e) {
System.out.println("Null element encountered");
}
Order Matters!
Catch the most specific exception first:
// ✅ CORRECT — specific first
catch (FileNotFoundException e) { ... }
catch (IOException e) { ... }
catch (Exception e) { ... }
// ❌ WRONG — parent catches everything, child is unreachable
catch (Exception e) { ... }
catch (IOException e) { ... } // compile error: unreachable
Multi-catch (Java 7+)
Handle multiple unrelated exceptions with one block:
try {
// risky code
} catch (IOException | SQLException e) {
System.out.println("Data error: " + e.getMessage());
// e is implicitly final — you cannot reassign it
}
Rules:
- Exception types must not be related (no parent-child).
- The variable
eis effectively final.
try-catch Without finally
finally is optional:
try {
int n = Integer.parseInt("abc");
} catch (NumberFormatException e) {
System.out.println("Invalid number");
}
try-finally Without catch
Also valid — ensures cleanup even if you don’t handle the exception:
Lock lock = new ReentrantLock();
lock.lock();
try {
// critical section
} finally {
lock.unlock(); // always release the lock
}
Nested try-catch
try {
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("Inner catch: " + e.getMessage());
throw new RuntimeException("Wrapped", e); // rethrow
}
} catch (RuntimeException e) {
System.out.println("Outer catch: " + e.getMessage());
}
Avoid deep nesting — extract into methods instead.
What Happens When finally Throws?
try {
throw new RuntimeException("from try");
} finally {
throw new RuntimeException("from finally"); // replaces the try exception!
}
// Only "from finally" propagates — the original exception is LOST
Warning: Never throw from
finally. It masks the original exception.
Execution Flow Visualised
try {
statement 1 ──→ OK ──→ statement 2 ──→ OK ──→ statement 3
│ │
exception exception
│ │
▼ ▼
matching catch? matching catch?
yes → catch block yes → catch block
no → propagate up no → propagate up
│ │
└───────┬───────────────┘
▼
finally block (always)
│
▼
next statement (if exception was handled)
Common Mistakes
| Mistake | Problem | Fix |
|---|---|---|
| Empty catch block | Exception silently swallowed | Log or rethrow |
Catching Exception |
Too broad, hides bugs | Catch specific types |
Logic in finally |
May mask exceptions | Keep cleanup only |
| Variable scope | try-scoped vars invisible in catch |
Declare before try |
Returning in finally |
Overrides try/catch return |
Avoid returns in finally |
// BAD — swallowed exception
catch (Exception e) { }
// GOOD — at minimum, log it
catch (IOException e) {
logger.error("Failed to read config", e);
}
Key Takeaways
trywraps risky code;catchhandles specific exceptions;finallyalways runs.- Catch specific exceptions, ordered from most specific to most general.
finallyis for cleanup — don’t put business logic or return statements there.- Use multi-catch (
IOException | SQLException) for unrelated exceptions with the same handling. - Never write an empty catch block — at minimum, log the exception.
What’s Next?
Next: throw and throws — creating and declaring exceptions, rethrowing, and exception chaining.