Exception Handling

An exception is an event that disrupts the normal flow of a program — dividing by zero, accessing an array index that doesn't exist, or calling a method on a null reference, for example. Instead of letting the program crash, Java lets you catch these events and decide what to do next.

try, catch, and finally

Code that might throw an exception goes inside a try block. If an exception occurs, control jumps to a matching catch block. A finally block, if present, always runs afterward — whether an exception occurred or not — which makes it ideal for cleanup work like closing a file or a database connection.

try {
  int result = 10 / 0; // throws ArithmeticException
} catch (ArithmeticException e) {
  System.out.println("Can't divide by zero: " + e.getMessage());
} finally {
  System.out.println("This always runs");
}

Here, dividing by zero throws an ArithmeticException. The catch block handles it gracefully by printing a message instead of crashing, and the finally block runs regardless of what happened above it.

Note: You can chain multiple catch blocks after one try to handle different exception types differently. Java checks them in order and runs the first one that matches.

Throwing an Exception Manually

You can raise an exception yourself with the throw keyword, followed by a new exception object:

throw new IllegalArgumentException("Value cannot be negative");

This is useful for enforcing your own rules — for example, rejecting an invalid input before it causes problems further down the line.

Checked vs Unchecked Exceptions

Java exceptions fall into two broad categories:

  • Checked exceptions — must either be caught with a try/catch or declared on the method with throws. The compiler enforces this. Example: IOException, which can occur when reading a file.
  • Unchecked exceptions (runtime exceptions) — don't require a throws declaration or a mandatory catch. They usually signal a programming mistake. Examples: NullPointerException, ArithmeticException.

Creating a Custom Exception

You can define your own exception type by extending Exception (checked) or RuntimeException (unchecked). This lets your error messages be as specific as your own business rules:

class InvalidAgeException extends Exception {
  InvalidAgeException(String message) {
    super(message); // hands the message up to Exception
  }
}

Worked Example: Validating Age

Here's a method that throws our custom exception when it receives an invalid age, called from inside a try/catch:

public class AgeValidator {
  static void checkAge(int age) throws InvalidAgeException {
    if (age < 0) {
      throw new InvalidAgeException("Age cannot be negative: " + age);
    }
    System.out.println("Age is valid");
  }

  public static void main(String[] args) {
    try {
      checkAge(-5);
    } catch (InvalidAgeException e) {
      System.out.println("Caught: " + e.getMessage());
    }
  }
}

Because InvalidAgeException extends Exception, it's a checked exception, so checkAge must declare throws InvalidAgeException. Calling it with -5 triggers the throw, and the catch block in main prints: Caught: Age cannot be negative: -5.

Quick recap:

  • try/catch — attempt risky code, handle failures gracefully.
  • finally — always runs, ideal for cleanup.
  • throw — raise an exception manually.
  • Checked exceptions must be declared or caught; unchecked ones don't require this.
  • Custom exceptions extend Exception (checked) or RuntimeException (unchecked).
Next steps: Next up is the Collections Framework — the toolkit Java gives you for storing and managing groups of objects.