MindIQ Academy

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

  1. What Is an Exception?
  2. The Exception Hierarchy
  3. Checked vs Unchecked Exceptions
  4. Error vs Exception
  5. try-catch
  6. Multiple catch & Multi-Catch
  7. finally
  8. try-with-resources
  9. throw vs throws
  10. Exception Propagation
  11. Custom Exceptions
  12. Common Runtime Exceptions
  13. Compilation Errors with Exceptions
  14. Certification Traps
  15. Common Mistakes
  16. Interview Questions
  17. Summary Tables
  18. Quick Revision Notes
  19. 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.

TermMeaning
ThrowAn exception object is created and "thrown."
CatchCode that handles the thrown exception.
Stack traceThe chain of method calls when the exception occurred.
PropagationAn 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
ClassTypeCatch it?
ThrowablerootTechnically yes (not recommended)
ErroruncheckedNo (JVM-level, unrecoverable)
Exceptionchecked*Yes
RuntimeExceptionuncheckedYes (but usually a bug to fix)

*Exception is checked except for RuntimeException and 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 Exception but not RuntimeException.
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 (or Error).
  • 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

FeatureCheckedUnchecked
Checked at compile time✅ Yes❌ No
Must catch or declare✅ Yes❌ No
SuperclassException (not RTE)RuntimeException / Error
RepresentsRecoverable conditionsProgramming bugs
ExamplesIOException, SQLExceptionNullPointerException, ArithmeticException

4. Error vs Exception

AspectErrorException
MeaningSerious JVM problemsApplication-level issues
Recoverable?NoOften yes
Should you catch?NoYes
TypeUncheckedChecked or unchecked
ExamplesOutOfMemoryError, StackOverflowErrorIOException, 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. Catch Exception (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

RuleDetail
try needs catch or finallyA lone try won't compile.
Catch a specific typeThe catch parameter must be Throwable or a subtype.
Variable scopeThe catch variable exists only in that catch block.
After handlingExecution 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 ruleDetail
Types must be disjointCannot list a type and its subclass: IOException | FileNotFoundException = error.
Variable is effectively finalYou cannot reassign e.
One block, multiple typesReduces 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?

Scenariofinally 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 (or throw) in finally replaces any value/exception from try/catch — a notorious exam trap. Avoid returning from finally.


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

RuleDetail
Resource must be AutoCloseableOr Closeable (which extends it).
Auto-closedIn reverse order of declaration.
Closed before catch/finallyResources close first, then catch runs.
Effectively finalJava 9+ allows existing final/effectively-final vars.
Suppressed exceptionsClose-time exceptions are suppressed, not lost.

9. throw vs throws

KeywordPurposeWhere
throwThrows an exception object nowInside a method body
throwsDeclares that a method may throwIn 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 typePropagation rule
CheckedEach method in the chain must throws or catch it.
UncheckedPropagates 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

PracticeReason
Extend RuntimeException for programming errorsNo forced handling.
Extend Exception for recoverable conditionsForces the caller to handle.
Provide a (String message) constructorUseful error messages.
Provide a (String, Throwable) constructorPreserve the original cause (chaining).
End the name with ExceptionConvention.

12. Common Runtime Exceptions

ExceptionCauseExample
NullPointerExceptionUsing a null referenceString s = null; s.length();
ArrayIndexOutOfBoundsExceptionBad array indexarr[arr.length]
StringIndexOutOfBoundsExceptionBad String index"hi".charAt(5)
ArithmeticExceptionInteger divide by zero5 / 0
ClassCastExceptionInvalid cast(String) anInteger
NumberFormatExceptionBad number parseInteger.parseInt("abc")
IllegalArgumentExceptionInvalid argumentThread.sleep(-1)
IllegalStateExceptionInvalid object statereusing a consumed stream
ConcurrentModificationExceptionModifying a collection during iterationfor-each + remove
UnsupportedOperationExceptionMutating an immutable collectionList.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.

CodeError
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/Exception even 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
1Checked exceptions must be caught or declared, or the code won't compile.
2RuntimeException and Error are unchecked — no declaration required.
3catch blocks must go specific → general; reverse = compile error.
4Multi-catch types must be disjoint (no subclass/superclass pairs).
5The multi-catch variable is effectively final (cannot reassign).
6finally always runs — except System.exit(), JVM crash.
7A return/throw in finally overrides the try/catch result.
8try-with-resources closes in reverse order, before catch/finally.
9Close-time exceptions are suppressed, not the primary exception.
10Catching a checked exception never thrown = compile error (not for runtime types).
11throw null; compiles but throws NullPointerException.
12A lone try (no catch/finally/resource) won't compile.
13Code after an unconditional throw/return is unreachable = compile error.
14An overriding method cannot throw broader checked exceptions than the parent.
15Error should never be caught — it's unrecoverable.

15. Common Mistakes

MistakeFix
Swallowing exceptions (empty catch)At least log; never silently ignore.
Catching Exception for everythingCatch specific types you can handle.
Returning from finallyAvoid — it discards real results/exceptions.
Manually closing resources in finallyUse try-with-resources instead.
Catching Throwable/ErrorDon't catch unrecoverable errors.
Ordering general catch before specificSpecific first, general last.
Losing the original causeUse new XException(msg, cause).
Using exceptions for normal control flowUse 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

CategorySuperclassChecked?Catch?
ErrorsErrorNoNo
Runtime exceptionsRuntimeExceptionNoOptional
Other exceptionsExceptionYesRequired (catch/declare)

Block Behavior

BlockPurposeRequired?
tryWrap risky codeYes (to start)
catchHandle exceptionIf no finally/resource
finallyAlways-run cleanupOptional
try-with-resourcesAuto-closeOptional

Keyword Roles

KeywordRole
tryBegin protected block
catchHandle a thrown exception
finallyGuaranteed cleanup
throwRaise an exception now
throwsDeclare possible exceptions

18. Quick Revision Notes

  • All throwables descend from ThrowableError (unchecked) and Exception.
  • Checked = compile-time checked, must catch or declare (e.g., IOException).
  • Unchecked = RuntimeException/Error subclasses; no declaration needed.
  • Never catch Error; it's unrecoverable.
  • try needs catch, finally, or a resource.
  • Catch order: specific → general; reverse is a compile error.
  • Multi-catch: disjoint types only; variable is effectively final.
  • finally always runs except System.exit()/JVM crash; return in finally overrides.
  • try-with-resources: AutoCloseable, closes in reverse order, before catch/finally; close exceptions are suppressed.
  • throw raises now; throws declares; 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) or RuntimeException (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.