ZetCode

Java Exception Class

Last modified: April 13, 2025

The java.lang.Exception class is the superclass of all exceptions in Java. Exceptions are events that disrupt the normal flow of program execution. They indicate problems that need to be handled for robust application development.

Java exceptions are divided into checked exceptions (compile-time) and unchecked exceptions (runtime). The Exception class forms the root of the checked exception hierarchy. Understanding exception handling is crucial for writing reliable code.

Exception Class Hierarchy

The Exception class sits below Throwable in Java's exception hierarchy. It has two main subclasses: RuntimeException (unchecked) and IOException (checked). This distinction affects how exceptions must be handled in code.

Throwable
├── Error
└── Exception
    ├── RuntimeException
    │   ├── NullPointerException
    │   ├── IllegalArgumentException
    │   └── ...
    ├── IOException
    │   ├── FileNotFoundException
    │   └── ...
    └── Other checked exceptions

The diagram shows Java's exception hierarchy. Errors are serious problems that applications shouldn't try to catch. Exceptions represent conditions that programs might want to catch and handle.

Basic Exception Handling

Java provides try-catch blocks to handle exceptions. The try block contains code that might throw an exception. The catch block contains code to handle the exception if it occurs. This prevents program termination.

Main.java
package com.zetcode;

public class Main {

    public static void main(String[] args) {
        try {
            int result = divide(10, 0);
            System.out.println("Result: " + result);
        } catch (ArithmeticException e) {
            System.out.println("Error: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    public static int divide(int a, int b) {
        return a / b;
    }
}

This example demonstrates basic exception handling. The divide method throws an ArithmeticException when dividing by zero. The try-catch block catches this exception, prints an error message, and shows the stack trace. The program continues execution after handling the exception.

Checked vs Unchecked Exceptions

Checked exceptions must be declared or handled, while unchecked exceptions don't. Checked exceptions extend Exception but not RuntimeException. Unchecked exceptions extend RuntimeException or Error.

Main.java
package com.zetcode;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        // Unchecked exception (no need to declare)
        try {
            String str = null;
            System.out.println(str.length());
        } catch (NullPointerException e) {
            System.out.println("NullPointerException caught");
        }
        
        // Checked exception (must be handled)
        try {
            readFile("nonexistent.txt");
        } catch (FileNotFoundException e) {
            System.out.println("File not found: " + e.getMessage());
        }
    }
    
    public static void readFile(String path) throws FileNotFoundException {
        File file = new File(path);
        Scanner scanner = new Scanner(file);
        while (scanner.hasNextLine()) {
            System.out.println(scanner.nextLine());
        }
        scanner.close();
    }
}

This example contrasts checked and unchecked exceptions. The NullPointerException is unchecked and doesn't require declaration. The FileNotFoundException is checked and must be either caught or declared in the method signature using throws.

Creating Custom Exceptions

Custom exceptions can be created by extending Exception or RuntimeException. They should provide constructors matching superclass constructors. Custom exceptions are useful for application-specific error conditions.

Main.java
package com.zetcode;

class InsufficientFundsException extends Exception {
    private double amount;
    
    public InsufficientFundsException(double amount) {
        super("Insufficient funds: " + amount);
        this.amount = amount;
    }
    
    public double getAmount() {
        return amount;
    }
}

class BankAccount {
    private double balance;
    
    public BankAccount(double balance) {
        this.balance = balance;
    }
    
    public void withdraw(double amount) throws InsufficientFundsException {
        if (amount > balance) {
            throw new InsufficientFundsException(amount - balance);
        }
        balance -= amount;
    }
}

public class Main {

    public static void main(String[] args) {
        BankAccount account = new BankAccount(500);
        
        try {
            account.withdraw(600);
        } catch (InsufficientFundsException e) {
            System.out.println(e.getMessage());
            System.out.println("Missing amount: " + e.getAmount());
        }
    }
}

This example shows a custom InsufficientFundsException. The exception includes additional information about the missing amount. The BankAccount class throws this exception when withdrawal exceeds balance. The main method catches and handles the custom exception.

Multiple Catch Blocks

Multiple catch blocks can handle different exception types. More specific exceptions should come before more general ones. Java 7 introduced multi-catch for handling multiple exceptions in one block.

Main.java
package com.zetcode;

import java.util.InputMismatchException;
import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        try {
            System.out.print("Enter numerator: ");
            int numerator = scanner.nextInt();
            
            System.out.print("Enter denominator: ");
            int denominator = scanner.nextInt();
            
            int result = numerator / denominator;
            System.out.println("Result: " + result);
            
        } catch (InputMismatchException e) {
            System.out.println("Invalid input - must be integer");
        } catch (ArithmeticException e) {
            System.out.println("Cannot divide by zero");
        } catch (Exception e) {
            System.out.println("An unexpected error occurred");
        } finally {
            scanner.close();
            System.out.println("Scanner closed in finally block");
        }
    }
}

This example demonstrates multiple catch blocks. InputMismatchException handles non-integer input. ArithmeticException handles division by zero. The more general Exception catches any other errors. The finally block ensures the scanner is always closed, regardless of exceptions.

Try-With-Resources

Try-with-resources automatically closes resources that implement AutoCloseable. This eliminates the need for explicit finally blocks for resource cleanup. It was introduced in Java 7 to simplify resource management.

Main.java
package com.zetcode;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Main {

    public static void main(String[] args) {
        String path = "example.txt";
        
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("Error reading file: " + e.getMessage());
        }
    }
}

This example uses try-with-resources to read a file. The BufferedReader is declared in the try statement and automatically closed when the block exits. This approach is cleaner than manual resource management with finally blocks. The IOException is caught if file reading fails.

Exception Propagation

Exceptions propagate up the call stack until caught. Methods can declare exceptions they throw but don't handle. This allows centralized exception handling at appropriate levels in the application.

Main.java
package com.zetcode;

class DataProcessor {
    public void processData(String data) throws IllegalArgumentException {
        if (data == null || data.isEmpty()) {
            throw new IllegalArgumentException("Invalid data");
        }
        System.out.println("Processing: " + data);
    }
}

class DataService {
    private DataProcessor processor = new DataProcessor();
    
    public void handleData(String data) {
        try {
            processor.processData(data);
        } catch (IllegalArgumentException e) {
            System.out.println("Service error: " + e.getMessage());
            // Could log error or perform recovery here
        }
    }
}

public class Main {

    public static void main(String[] args) {
        DataService service = new DataService();
        
        service.handleData("Valid data");
        service.handleData(""); // Will trigger exception
    }
}

This example shows exception propagation. The DataProcessor throws an IllegalArgumentException for invalid data. The DataService catches and handles this exception. The main method doesn't need to handle the exception because it's handled at the service level. This demonstrates layered exception handling.

Source

Java Exception Class Documentation

In this article, we've covered the Java Exception class and exception handling with practical examples. Proper exception handling is essential for building robust and maintainable Java applications.

Author

My name is Jan Bodnar, and I am a dedicated programmer with many years of experience in the field. I began writing programming articles in 2007 and have since authored over 1,400 articles and eight e-books. With more than eight years of teaching experience, I am committed to sharing my knowledge and helping others master programming concepts.

List all Java tutorials.