🌱BeginnerObject-Oriented Programming

Exception Handling

Handle runtime errors gracefully with try-catch-finally, throw exceptions, and understand checked vs unchecked exceptions.

Overview

Exceptions are runtime errors - things that go wrong while the program is running: dividing by zero, accessing a null reference, reading a file that doesn't exist. Without exception handling, these cause the program to crash with an error message. Java's try-catch mechanism lets you catch exceptions and respond gracefully instead of crashing. A try block contains code that might fail. A catch block catches and handles a specific exception type. A finally block always runs, regardless of whether an exception occurred - useful for cleanup (closing files, connections). You can also throw your own exceptions using throw new ExceptionType("message").

Key Points

  • •try block: code that might throw an exception
  • •catch (ExceptionType e): catches and handles that type of exception
  • •finally: always runs - use for cleanup (close files, connections)
  • •throw: manually throw an exception
  • •Checked exceptions (IOException, SQLException): must be caught or declared with throws
  • •Unchecked exceptions (RuntimeException subclasses): ArithmeticException, NullPointerException, ArrayIndexOutOfBoundsException - optional to catch
  • •catch blocks are matched in order - put specific types before general ones
  • •Exception message: e.getMessage(), stack trace: e.printStackTrace()

Examples

Basic try-catch and finally

public class Main {
    public static void main(String[] args) {
        // ArithmeticException: division by zero
        try {
            int result = 10 / 0;
            System.out.println(result); // never reached
        } catch (ArithmeticException e) {
            System.out.println("Cannot divide by zero: " + e.getMessage());
        } finally {
            System.out.println("This always runs.");
        }

        // NullPointerException
        try {
            String s = null;
            System.out.println(s.length());
        } catch (NullPointerException e) {
            System.out.println("Null reference: " + e.getMessage());
        }

        System.out.println("Program continues normally.");
    }
}

finally runs whether or not an exception was caught. Code after the try-catch block continues normally after a caught exception.

Multiple catch blocks and catching multiple types

public class Main {
    static int divide(int a, int b) {
        return a / b; // throws ArithmeticException if b=0
    }

    static int getElement(int[] arr, int index) {
        return arr[index]; // throws ArrayIndexOutOfBoundsException if out of range
    }

    public static void main(String[] args) {
        int[] numbers = {10, 20, 30};

        try {
            System.out.println(divide(10, 0));
        } catch (ArithmeticException e) {
            System.out.println("Math error: " + e.getMessage());
        }

        try {
            System.out.println(getElement(numbers, 10));
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Index out of range: " + e.getMessage());
        }

        // Catching multiple in one block (Java 7+)
        try {
            String s = null;
            s.length();
        } catch (NullPointerException | IllegalArgumentException e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}

Specific exception types before general ones. `catch (TypeA | TypeB e)` catches either type in one block.

Throwing exceptions and custom validation

class BankAccount {
    private double balance;

    BankAccount(double initial) {
        if (initial < 0) {
            throw new IllegalArgumentException("Initial balance cannot be negative: " + initial);
        }
        this.balance = initial;
    }

    void withdraw(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Withdrawal amount must be positive.");
        }
        if (amount > balance) {
            throw new ArithmeticException("Insufficient funds. Balance: " + balance);
        }
        balance -= amount;
        System.out.printf("Withdrew $%.2f. Balance: $%.2f%n", amount, balance);
    }

    double getBalance() { return balance; }
}

public class Main {
    public static void main(String[] args) {
        try {
            BankAccount acc = new BankAccount(100.0);
            acc.withdraw(30.0);
            acc.withdraw(200.0); // throws
        } catch (ArithmeticException e) {
            System.out.println("Transaction failed: " + e.getMessage());
        } catch (IllegalArgumentException e) {
            System.out.println("Invalid input: " + e.getMessage());
        }
    }
}

throw creates and throws an exception immediately. Execution jumps out of the current method and up the call stack until a matching catch is found.

Exception hierarchy - catching with a supertype

public class Main {
    public static void main(String[] args) {
        try {
            String s = null;
            s.length(); // NullPointerException
        } catch (RuntimeException e) {
            // Catches NullPointerException (and any other RuntimeException)
            System.out.println("Runtime error: " + e.getClass().getSimpleName());
            System.out.println("Message: " + e.getMessage());
        }

        // Exception is the supertype of all exceptions
        try {
            int[] arr = new int[5];
            arr[10] = 1;
        } catch (Exception e) {
            System.out.println("Caught: " + e.getClass().getSimpleName());
        }
    }
}

Catching a supertype (Exception) catches all subtypes. Prefer specific types when possible so you handle each case appropriately.

Common Mistakes

  • Catching Exception (the broadest type) everywhere - you lose information about what actually went wrong and may silently swallow bugs
  • Empty catch blocks: `catch (Exception e) {}` - the error is silently ignored, making bugs very hard to find
  • Putting too much code in try blocks - keep try blocks small, covering only the lines that might throw
  • Putting specific catch blocks after general ones - `catch (Exception e)` before `catch (NullPointerException e)` makes the specific one unreachable

Note

Handle exceptions at the level where you can do something meaningful about them. If you can't handle it, let it propagate up. Always log or print the exception details - a silent catch is almost always wrong.

Exercises

Write Java code and click Run to execute it instantly. When your output looks correct, mark as done.

Loading editor…

Completed: 0 / 4 exercises

Knowledge Check

Question 1 of 4

When does the finally block run?