07 - Exception Handling Made Easy
A complete beginner-to-advanced guide to Java exception handling, aligned with the Oracle Certified Professional: Java SE 21 Developer (1Z0-830) exam objectives.
Table of Contents
- What Is an Exception?
- The Exception Hierarchy
- Checked vs Unchecked Exceptions
- Error vs Exception
- try-catch
- Multiple catch & Multi-Catch
- finally
- try-with-resources
- throw vs throws
- Exception Propagation
- Custom Exceptions
- Common Runtime Exceptions
- Compilation Errors with Exceptions
- Certification Traps
- Common Mistakes
- Interview Questions
- Summary Tables
- Quick Revision Notes
- One-Page Cheat Sheet
1. What Is an Exception?
An exception is an event that disrupts the normal flow of a program. Instead of crashing, Java lets you catch and handle these events gracefully.
public class Demo {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
System.out.println(arr[5]); // ArrayIndexOutOfBoundsException
System.out.println("This line never runs");
}
}
Real-world analogy: An exception is like a smoke alarm — it interrupts everything and demands attention. A try-catch is the plan you have ready to respond.
| Term | Meaning |
|---|---|
| Throw | An exception object is created and "thrown." |
| Catch | Code that handles the thrown exception. |
| Stack trace | The chain of method calls when the exception occurred. |
| Propagation | An uncaught exception travels up the call stack. |
2. The Exception Hierarchy
Everything throwable descends from java.lang.Throwable.
Throwable
/ \
Error Exception
(unchecked) / \
RuntimeException IOException, SQLException...
(unchecked) (CHECKED)
/ | \
NullPointer Arithmetic ArrayIndexOutOfBounds ...
The Big Picture
java.lang.Object
|
Throwable ............................ root of all errors/exceptions
/ \
Error Exception
| |
| +-- RuntimeException -> UNCHECKED (and its subclasses)
| +-- IOException -> CHECKED
| +-- SQLException -> CHECKED
|
+-- OutOfMemoryError -> UNCHECKED (serious JVM problems)
+-- StackOverflowError
| Class | Type | Catch it? |
|---|---|---|
Throwable | root | Technically yes (not recommended) |
Error | unchecked | No (JVM-level, unrecoverable) |
Exception | checked* | Yes |
RuntimeException | unchecked | Yes (but usually a bug to fix) |
*
Exceptionis checked except forRuntimeExceptionand its subclasses.
3. Checked vs Unchecked Exceptions
This distinction is the single most tested exception topic.
Checked Exceptions
- Checked at compile time.
- Must be either caught (
try-catch) or declared (throws). - Represent recoverable conditions (file missing, network failure).
- Subclasses of
Exceptionbut notRuntimeException.
import java.io.*;
public class FileReaderDemo {
public static void main(String[] args) throws IOException { // declared
FileReader fr = new FileReader("data.txt"); // may throw FileNotFoundException
}
}
If you remove throws IOException and don't catch it → compile error: "unreported exception IOException; must be caught or declared to be thrown."
Unchecked Exceptions
- Not checked at compile time.
- Subclasses of
RuntimeException(orError). - Represent programming bugs (null access, bad index, divide by zero).
- No requirement to catch or declare.
public class Demo {
public static void main(String[] args) {
String s = null;
System.out.println(s.length()); // NullPointerException (unchecked)
}
}
Comparison
| Feature | Checked | Unchecked |
|---|---|---|
| Checked at compile time | ✅ Yes | ❌ No |
| Must catch or declare | ✅ Yes | ❌ No |
| Superclass | Exception (not RTE) | RuntimeException / Error |
| Represents | Recoverable conditions | Programming bugs |
| Examples | IOException, SQLException | NullPointerException, ArithmeticException |
4. Error vs Exception
| Aspect | Error | Exception |
|---|---|---|
| Meaning | Serious JVM problems | Application-level issues |
| Recoverable? | No | Often yes |
| Should you catch? | No | Yes |
| Type | Unchecked | Checked or unchecked |
| Examples | OutOfMemoryError, StackOverflowError | IOException, NullPointerException |
// Error example (do NOT try to handle this):
public class Boom {
public static void main(String[] args) {
recurse(0); // StackOverflowError
}
static void recurse(int n) { recurse(n + 1); }
}
Rule: Never catch
Error— it signals an unrecoverable JVM state. CatchException(or specific subclasses).
5. try-catch
The try block holds risky code; the catch block handles the exception.
public class Divide {
public static void main(String[] args) {
try {
int result = 10 / 0; // throws ArithmeticException
System.out.println(result); // skipped
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero: " + e.getMessage());
}
System.out.println("Program continues"); // runs
}
}
Output:
Cannot divide by zero: / by zero
Program continues
Flow Diagram
try {
risky code --- exception? ---> catch (matching type) { handle }
normal code |
} v
continue after try-catch
Rules
| Rule | Detail |
|---|---|
try needs catch or finally | A lone try won't compile. |
| Catch a specific type | The catch parameter must be Throwable or a subtype. |
| Variable scope | The catch variable exists only in that catch block. |
| After handling | Execution continues after the whole try-catch. |
6. Multiple catch & Multi-Catch
Multiple catch Blocks
try {
process();
} catch (FileNotFoundException e) { // most specific first
System.out.println("File not found");
} catch (IOException e) { // more general after
System.out.println("IO error");
} catch (Exception e) { // most general last
System.out.println("General error");
}
Ordering Trap (Compile Error)
try {
risky();
} catch (Exception e) { // general FIRST
// ...
} catch (IOException e) { // ERROR: already caught by Exception above
// ...
}
A subclass catch after its superclass catch is unreachable → compile error. Order: most specific → most general.
Multi-Catch (Java 7+)
Handle several unrelated exceptions in one block with |:
try {
risky();
} catch (IOException | SQLException e) { // multi-catch
System.out.println("Handled: " + e.getMessage());
}
| Multi-catch rule | Detail |
|---|---|
| Types must be disjoint | Cannot list a type and its subclass: IOException | FileNotFoundException = error. |
| Variable is effectively final | You cannot reassign e. |
| One block, multiple types | Reduces duplicate handling code. |
// ERROR: FileNotFoundException is a subclass of IOException
// catch (IOException | FileNotFoundException e) { }
7. finally
The finally block always runs — whether or not an exception occurred. Use it for cleanup (closing files, releasing locks).
try {
System.out.println("try");
throw new RuntimeException("boom");
} catch (RuntimeException e) {
System.out.println("catch");
} finally {
System.out.println("finally"); // ALWAYS runs
}
// Output: try / catch / finally
When Does finally NOT Run?
| Scenario | finally runs? |
|---|---|
| Normal completion | ✅ Yes |
| Exception caught | ✅ Yes |
| Exception uncaught | ✅ Yes (before propagating) |
return in try/catch | ✅ Yes (before returning) |
System.exit(0) | ❌ No |
| JVM crash / power off | ❌ No |
| Infinite loop / daemon kill | ❌ No |
The finally Override Trap
static int test() {
try {
return 1;
} finally {
return 2; // OVERRIDES the try's return!
}
}
// test() returns 2
A
return(orthrow) infinallyreplaces any value/exception fromtry/catch— a notorious exam trap. Avoid returning fromfinally.
8. try-with-resources
Introduced in Java 7, try-with-resources automatically closes resources that implement AutoCloseable. No manual finally needed.
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
System.out.println(br.readLine());
} // br.close() called automatically, even on exception
Multiple Resources & Close Order
try (ResourceA a = new ResourceA();
ResourceB b = new ResourceB()) {
// use a and b
}
// closed in REVERSE order: b.close() first, then a.close()
Effectively-Final Resources (Java 9+)
BufferedReader br = new BufferedReader(new FileReader("data.txt"));
try (br) { // Java 9+: can use an existing effectively-final var
System.out.println(br.readLine());
}
Suppressed Exceptions
If both the try body and close() throw, the body's exception is primary and the close() exception is suppressed (retrievable via getSuppressed()).
try (AutoCloseable r = () -> { throw new RuntimeException("close fail"); }) {
throw new RuntimeException("body fail"); // primary
} catch (Exception e) {
System.out.println(e.getMessage()); // body fail
for (Throwable t : e.getSuppressed())
System.out.println("Suppressed: " + t.getMessage()); // close fail
}
Rules
| Rule | Detail |
|---|---|
Resource must be AutoCloseable | Or Closeable (which extends it). |
| Auto-closed | In reverse order of declaration. |
| Closed before catch/finally | Resources close first, then catch runs. |
| Effectively final | Java 9+ allows existing final/effectively-final vars. |
| Suppressed exceptions | Close-time exceptions are suppressed, not lost. |
9. throw vs throws
| Keyword | Purpose | Where |
|---|---|---|
throw | Throws an exception object now | Inside a method body |
throws | Declares that a method may throw | In the method signature |
public void validate(int age) throws IllegalAccessException { // declares
if (age < 0) {
throw new IllegalArgumentException("Negative age"); // throws now
}
}
// throw requires an instance:
throw new IOException("failed"); // OK
// throw IOException; // ERROR: needs an object
// throwing null:
throw null; // compiles, but throws NullPointerException
10. Exception Propagation
When a method doesn't catch an exception, it propagates up the call stack until caught or it reaches the JVM (which prints a stack trace and terminates the thread).
public class Propagation {
public static void main(String[] args) {
a(); // exception bubbles up to here
}
static void a() { b(); }
static void b() { c(); }
static void c() {
int x = 10 / 0; // ArithmeticException thrown here
}
}
Call stack (exception travels UP):
c() <- thrown here
b() <- not caught, propagates
a() <- not caught, propagates
main()<- not caught -> JVM prints stack trace, thread dies
| Exception type | Propagation rule |
|---|---|
| Checked | Each method in the chain must throws or catch it. |
| Unchecked | Propagates freely without declaration. |
11. Custom Exceptions
Create your own exception by extending Exception (checked) or RuntimeException (unchecked).
// Checked custom exception
class InsufficientFundsException extends Exception {
public InsufficientFundsException(String message) {
super(message);
}
}
// Unchecked custom exception
class InvalidInputException extends RuntimeException {
public InvalidInputException(String message) {
super(message);
}
}
Real-World Usage
class BankAccount {
private double balance;
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(
"Tried to withdraw " + amount + " but balance is " + balance);
}
balance -= amount;
}
}
Best Practices
| Practice | Reason |
|---|---|
Extend RuntimeException for programming errors | No forced handling. |
Extend Exception for recoverable conditions | Forces the caller to handle. |
Provide a (String message) constructor | Useful error messages. |
Provide a (String, Throwable) constructor | Preserve the original cause (chaining). |
End the name with Exception | Convention. |
12. Common Runtime Exceptions
| Exception | Cause | Example |
|---|---|---|
NullPointerException | Using a null reference | String s = null; s.length(); |
ArrayIndexOutOfBoundsException | Bad array index | arr[arr.length] |
StringIndexOutOfBoundsException | Bad String index | "hi".charAt(5) |
ArithmeticException | Integer divide by zero | 5 / 0 |
ClassCastException | Invalid cast | (String) anInteger |
NumberFormatException | Bad number parse | Integer.parseInt("abc") |
IllegalArgumentException | Invalid argument | Thread.sleep(-1) |
IllegalStateException | Invalid object state | reusing a consumed stream |
ConcurrentModificationException | Modifying a collection during iteration | for-each + remove |
UnsupportedOperationException | Mutating an immutable collection | List.of(1).add(2) |
Helpful NPE Messages (Java 14+)
Java 14+ gives detailed NPE messages telling you exactly which variable was null:
Cannot invoke "String.length()" because "<local1>" is null
13. Compilation Errors with Exceptions
These produce compile-time errors (not runtime). Know them cold.
| Code | Error |
|---|---|
| Checked exception not caught/declared | "unreported exception ... must be caught or declared" |
catch superclass before subclass | "exception ... has already been caught" |
| Multi-catch with related types | "types ... are subclasses of each other" |
| Catching an exception never thrown (checked) | "exception ... is never thrown in body of corresponding try" |
try without catch/finally | "'try' without 'catch', 'finally' or resource declarations" |
Unreachable code after throw/return | "unreachable statement" |
Example 1: "Never Thrown" (Checked Only)
try {
System.out.println("hi"); // throws nothing checked
} catch (IOException e) { // COMPILE ERROR: IOException never thrown here
}
This rule applies only to checked exceptions. You can catch
RuntimeException/Exceptioneven if not obviously thrown.
Example 2: Unreachable Code
void m() {
throw new RuntimeException();
// System.out.println("x"); // COMPILE ERROR: unreachable statement
}
14. Certification Traps
| # | Trap |
|---|---|
| 1 | Checked exceptions must be caught or declared, or the code won't compile. |
| 2 | RuntimeException and Error are unchecked — no declaration required. |
| 3 | catch blocks must go specific → general; reverse = compile error. |
| 4 | Multi-catch types must be disjoint (no subclass/superclass pairs). |
| 5 | The multi-catch variable is effectively final (cannot reassign). |
| 6 | finally always runs — except System.exit(), JVM crash. |
| 7 | A return/throw in finally overrides the try/catch result. |
| 8 | try-with-resources closes in reverse order, before catch/finally. |
| 9 | Close-time exceptions are suppressed, not the primary exception. |
| 10 | Catching a checked exception never thrown = compile error (not for runtime types). |
| 11 | throw null; compiles but throws NullPointerException. |
| 12 | A lone try (no catch/finally/resource) won't compile. |
| 13 | Code after an unconditional throw/return is unreachable = compile error. |
| 14 | An overriding method cannot throw broader checked exceptions than the parent. |
| 15 | Error should never be caught — it's unrecoverable. |
15. Common Mistakes
| Mistake | Fix |
|---|---|
| Swallowing exceptions (empty catch) | At least log; never silently ignore. |
Catching Exception for everything | Catch specific types you can handle. |
Returning from finally | Avoid — it discards real results/exceptions. |
Manually closing resources in finally | Use try-with-resources instead. |
Catching Throwable/Error | Don't catch unrecoverable errors. |
| Ordering general catch before specific | Specific first, general last. |
| Losing the original cause | Use new XException(msg, cause). |
| Using exceptions for normal control flow | Use conditionals; exceptions are for exceptional cases. |
16. Interview Questions
Q1. What is the difference between checked and unchecked exceptions?
Checked exceptions are verified at compile time and must be caught or declared; unchecked exceptions (RuntimeException/Error subclasses) are not checked and need no declaration.
Q2. What is the root of the exception hierarchy?
java.lang.Throwable, with two main branches: Error and Exception.
Q3. Difference between Error and Exception?
Error represents serious, unrecoverable JVM problems (don't catch); Exception represents conditions an application can often handle.
Q4. What is the difference between throw and throws?
throw actually throws an exception object inside a method; throws declares in the method signature that it might throw.
Q5. Does finally always execute?
Almost always — except when System.exit() is called, the JVM crashes, or the thread is killed.
Q6. What happens if you return inside both try and finally?
The finally return wins and overrides the try's return value.
Q7. What is try-with-resources and why use it?
A construct that auto-closes AutoCloseable resources in reverse order, eliminating manual finally cleanup and avoiding leaks.
Q8. What are suppressed exceptions?
Exceptions thrown during resource close() while another exception is already propagating; they are attached to the primary exception and retrievable via getSuppressed().
Q9. What are the rules for multi-catch? The listed types must be disjoint (no subclass/superclass relationship), and the catch variable is effectively final.
Q10. Why must catch blocks be ordered specific to general? A superclass catch placed first makes later subclass catches unreachable, which is a compile error.
Q11. Can an overriding method throw a broader checked exception? No — it can throw the same, a narrower, or no checked exception, but never a broader one.
Q12. How do you create a custom exception?
Extend Exception (checked) or RuntimeException (unchecked), and provide message/cause constructors.
17. Summary Tables
Exception Categories
| Category | Superclass | Checked? | Catch? |
|---|---|---|---|
| Errors | Error | No | No |
| Runtime exceptions | RuntimeException | No | Optional |
| Other exceptions | Exception | Yes | Required (catch/declare) |
Block Behavior
| Block | Purpose | Required? |
|---|---|---|
try | Wrap risky code | Yes (to start) |
catch | Handle exception | If no finally/resource |
finally | Always-run cleanup | Optional |
| try-with-resources | Auto-close | Optional |
Keyword Roles
| Keyword | Role |
|---|---|
try | Begin protected block |
catch | Handle a thrown exception |
finally | Guaranteed cleanup |
throw | Raise an exception now |
throws | Declare possible exceptions |
18. Quick Revision Notes
- All throwables descend from
Throwable→Error(unchecked) andException. - Checked = compile-time checked, must catch or declare (e.g.,
IOException). - Unchecked =
RuntimeException/Errorsubclasses; no declaration needed. - Never catch
Error; it's unrecoverable. tryneedscatch,finally, or a resource.- Catch order: specific → general; reverse is a compile error.
- Multi-catch: disjoint types only; variable is effectively final.
finallyalways runs exceptSystem.exit()/JVM crash;returninfinallyoverrides.- try-with-resources:
AutoCloseable, closes in reverse order, before catch/finally; close exceptions are suppressed. throwraises now;throwsdeclares;throw null→ NPE.- Checked exceptions propagate only if declared; unchecked propagate freely.
- Catching a checked exception never thrown = compile error (not for runtime types).
- Overriding method: same/narrower/no checked exceptions, never broader.
- Custom: extend
Exception(checked) orRuntimeException(unchecked).
19. One-Page Cheat Sheet
======================= EXCEPTION HANDLING CHEAT SHEET =======================
HIERARCHY
Throwable
├─ Error (UNCHECKED, don't catch) OutOfMemoryError, StackOverflowError
└─ Exception
├─ RuntimeException (UNCHECKED) NPE, Arithmetic, ClassCast, IndexOOB
└─ IOException, SQLException... (CHECKED)
CHECKED vs UNCHECKED
checked -> compile-time check -> MUST catch or declare (throws)
unchecked -> RuntimeException/Error -> no declaration needed
try-catch-finally
try { risky } catch(SpecificEx e){...} finally { cleanup }
catch order: SPECIFIC -> GENERAL (reverse = compile error)
try alone = compile error (need catch/finally/resource)
MULTI-CATCH (Java 7+)
catch (IOException | SQLException e) {...}
types must be DISJOINT ; e is effectively final
(IOException | FileNotFoundException = ERROR, subclass)
finally
ALWAYS runs EXCEPT System.exit() / JVM crash
return/throw in finally OVERRIDES try/catch result (avoid!)
try-with-resources (Java 7+, effectively-final 9+)
try (Res a = ...; Res b = ...) {...}
closes REVERSE order (b then a), BEFORE catch/finally
resource must be AutoCloseable
close-time exception -> SUPPRESSED (getSuppressed())
throw vs throws
throw new X("msg"); // raise now (needs an object)
void m() throws X {} // declare
throw null; -> NullPointerException
PROPAGATION
uncaught exception travels UP the call stack to JVM
checked: every method must throws/catch ; unchecked: free
CUSTOM
class MyEx extends Exception {} // checked
class MyEx extends RuntimeException {} // unchecked
add (String) and (String, Throwable) constructors
COMPILE ERRORS
unreported checked exception | already caught (order)
multi-catch related types | checked never thrown | unreachable after throw
overriding can't broaden checked exceptions
==============================================================================
End of 07 - Exception Handling Made Easy.