Exceptions are Java's mechanism for dealing with things going wrong at runtime - a missing file, invalid input, a network call that fails - without crashing the whole program.

try / catch / finally

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, error or not");
}

Checked vs unchecked exceptions

Checked exceptions (like IOException) must be either caught or declared with throws - the compiler enforces this. Unchecked exceptions (like NullPointerException, ArithmeticException) do not require this - they usually represent programming bugs rather than expected failure conditions.

public void readFile(String path) throws IOException {
    // method that might throw a checked exception must declare it
}

Throwing your own exceptions

public void setAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("Age cannot be negative");
    }
    this.age = age;
}

Custom exception classes

For domain-specific errors, define your own exception by extending Exception (checked) or RuntimeException (unchecked):

public class InsufficientBalanceException extends RuntimeException {
    public InsufficientBalanceException(String message) {
        super(message);
    }
}

// usage:
if (balance < amount) {
    throw new InsufficientBalanceException("Not enough funds");
}

Catch specific exception types, not a blanket catch (Exception e), whenever you can - it keeps you from accidentally swallowing bugs you did not anticipate.