Phase 2 · Object-Oriented JavaModule 12~38 min read

Exception Handling

Handle failure gracefully with try/catch/finally, custom exceptions, and try-with-resources.

What you'll learn

Things go wrong: files are missing, networks drop, users type nonsense. A robust program anticipates failure and recovers gracefully instead of crashing. Exception handling is how Java lets you separate "what to do" from "what to do when it breaks."

By the end you'll be able to:

  • Understand the exception hierarchy and errors vs exceptions
  • Tell apart checked and unchecked exceptions
  • Handle failures with try, catch, and finally
  • Signal problems with throw and declare them with throws
  • Auto-close resources with try-with-resources
  • Create custom exceptions and follow handling best practices

Errors vs exceptions

Everything throwable in Java descends from Throwable, which splits into two branches. Errors are severe, usually-unrecoverable JVM problems (like running out of memory) — you don't catch these. Exceptions are conditions your program can and should handle:

The Throwable hierarchy
Throwable
Error — serious, don't catch (OutOfMemoryError)
Exception — checked (IOException, SQLException)
RuntimeException — unchecked (NullPointer, Arithmetic…)

Checked vs unchecked exceptions

Exceptions come in two kinds, and the difference decides whether the compiler forces you to deal with them:

  • Checked exceptions (e.g. IOException, SQLException) represent foreseeable external problems. The compiler requires you to either catch them or declare them with throws.
  • Unchecked exceptions (subclasses of RuntimeException, e.g. NullPointerException, ArithmeticException) usually signal programming bugs. The compiler doesn't force handling — you fix the cause instead.

try, catch & finally

Wrap risky code in a try block. If an exception is thrown, execution jumps to a matching catch block, and the program keeps running instead of crashing:

TryCatch.java
try {
    int result = 10 / 0;          // throws ArithmeticException
    System.out.println(result);   // never reached
} catch (ArithmeticException e) {
    System.out.println("Can't divide by zero!");
}

System.out.println("Program continues normally");
The three blocks
try { risky code }
catch (…) { handle the failure }
finally { always runs — cleanup }

You can have several catch blocks for different exception types, or combine them with multi-catch (|). A finally block runs no matter what — success or failure — which makes it ideal for cleanup:

Finally.java
try {
    int[] arr = {1, 2, 3};
    System.out.println(arr[5]);   // out of bounds
} catch (ArithmeticException | ArrayIndexOutOfBoundsException e) {
    System.out.println("Caught: " + e.getMessage());
} finally {
    System.out.println("Finally always runs (cleanup here)");
}

throw & throws

Use throw to raise an exception yourself when something is wrong, and throws in a method signature to declare that it might throw a checked exception (passing responsibility to the caller). Don't confuse them: throw is an action; throws is a declaration.

Bank.java
class InsufficientFundsException extends Exception {
    InsufficientFundsException(String msg) { super(msg); }
}

public class Bank {
    // 'throws' declares this method might fail with that checked exception
    static void withdraw(double balance, double amount)
            throws InsufficientFundsException {
        if (amount > balance) {
            throw new InsufficientFundsException("Not enough money");
        }
        System.out.println("Withdrew " + amount);
    }

    public static void main(String[] args) {
        try {
            withdraw(100, 150);
        } catch (InsufficientFundsException e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}

try-with-resources

Resources like files, database connections, and network sockets must be closed, even if an error occurs. Rather than a fiddly finally block, declare the resource in try (...) — Java closes it automatically as soon as the block ends. Any resource that implements AutoCloseable works:

Resources.java
// The resource declared in ( ) is closed automatically —
// even if an exception is thrown. No finally block needed.
try (Scanner sc = new Scanner(new File("data.txt"))) {
    System.out.println(sc.nextLine());
} catch (IOException e) {
    System.out.println("Could not read the file");
}

Tip

Prefer try-with-resources over manual finally cleanup — it's shorter, and it can never forget to close or leak a resource.

Custom exceptions

When the built-in exceptions don't capture your domain, create your own by extending Exception (checked) or RuntimeException (unchecked), as you saw with InsufficientFundsException above. A well-named custom exception makes error handling self-documenting. You can also chain exceptions — throw new MyException("...", cause) — to preserve the original cause.

Best practices

  • Catch specific exceptions, not a blanket catch (Exception e)
  • Never swallow exceptions with an empty catch block — at minimum, log them
  • Don't use exceptions for normal control flow — they're for exceptional cases
  • Clean up with try-with-resources rather than manual code
  • Add helpful messages so failures are easy to diagnose

The silent killer

An empty catch { } hides bugs and makes problems impossible to trace. If you truly can't handle an exception, let it propagate — don't bury it.

Recap & quick check

Key takeaways

  • Throwable splits into Error (don't catch) and Exception (handle).
  • Checked exceptions must be caught or declared; unchecked (RuntimeException) needn't be.
  • try runs risky code, catch handles failures, finally always runs for cleanup.
  • throw raises an exception; throws declares that a method may throw one.
  • try-with-resources auto-closes AutoCloseable resources — prefer it over finally.

Quick check

1. Which block always runs, whether or not an exception occurred?

2. What must you do with a checked exception?

3. What's the difference between throw and throws?

4. NullPointerException is an example of a…

5. Why prefer try-with-resources?

Superb — you've completed Phase 2 and can now write robust, well-organised object-oriented Java. Next comes Phase 3 — Core Java, starting with Module 13 on generics.