Java Custom Exception Tutorial

In Java, exceptions are used to handle errors and unexpected conditions. While Java provides many built-in exceptions (like NullPointerException, ArithmeticException, etc.), sometimes you need to create your own custom exceptions to handle specific error scenarios unique to your application. Custom exceptions allow you to define meaningful error messages and create more robust, readable, and maintainable code.

In this tutorial, we will cover:

1. What is a Custom Exception?

A custom exception is a user-defined exception that extends either Exception (for checked exceptions) or RuntimeException (for unchecked exceptions). Custom exceptions are useful when you want to:

Handle application-specific errors.
Provide more meaningful error messages.
Simplify error handling logic.
Create a hierarchy of exceptions for different error types in your application.

2. How to Create a Custom Exception

To create a custom exception, you simply need to extend the Exception class (for checked exceptions) or the RuntimeException class (for unchecked exceptions). You can define custom constructors to pass meaningful messages and other details.

Basic Structure for a Custom Exception

class MyCustomException extends Exception {
    public MyCustomException(String message) {
        super(message);  // Pass the message to the superclass constructor
    }
}

3. Throwing and Catching a Custom Exception

Once you define a custom exception, you can throw it using the throw keyword and handle it in a try-catch block like any other exception.

Example 1: Creating and Throwing a Custom Exception

// Custom exception class
class AgeValidationException extends Exception {
    public AgeValidationException(String message) {
        super(message);
    }
}

public class CustomExceptionExample {
    public static void main(String[] args) {
        try {
            validateAge(15);  // This will throw a custom exception
        } catch (AgeValidationException e) {
            System.out.println("Caught exception: " + e.getMessage());
        }
    }

    // Method that throws the custom exception if the age is less than 18
    public static void validateAge(int age) throws AgeValidationException {
        if (age < 18) {
            throw new AgeValidationException("Age must be 18 or older.");
        } else {
            System.out.println("Valid age: " + age);
        }
    }
}

Explanation:

AgeValidationException is a custom exception that extends Exception.
The validateAge() method checks the age and throws an AgeValidationException if the age is less than 18.
The exception is caught in the catch block and handled with an error message.

4. Checked vs. Unchecked Exceptions

Java exceptions are divided into two categories:

Checked Exceptions: These exceptions are checked at compile-time. They must either be caught or declared in the throws clause of the method. Custom exceptions extending Exception are checked exceptions.
Unchecked Exceptions: These exceptions are not checked at compile-time. They extend RuntimeException and can occur during the execution of the program. Custom exceptions extending RuntimeException are unchecked exceptions.

Example 2: Custom Unchecked Exception

// Custom unchecked exception class
class InvalidInputException extends RuntimeException {
    public InvalidInputException(String message) {
        super(message);
    }
}

public class UncheckedCustomExceptionExample {
    public static void main(String[] args) {
        int number = -5;

        try {
            checkPositiveNumber(number);
        } catch (InvalidInputException e) {
            System.out.println("Caught unchecked exception: " + e.getMessage());
        }
    }

    // Method that throws the unchecked custom exception if the number is negative
    public static void checkPositiveNumber(int number) {
        if (number < 0) {
            throw new InvalidInputException("Number must be positive.");
        } else {
            System.out.println("Valid number: " + number);
        }
    }
}

Explanation:

InvalidInputException is a custom unchecked exception that extends RuntimeException.
The checkPositiveNumber() method checks if the number is positive and throws an InvalidInputException if the number is negative.
Unlike checked exceptions, unchecked exceptions don't need to be declared in the method signature or handled explicitly.

5. Examples of Custom Exceptions

Example 3: Custom Exception with Additional Fields

You can add additional fields to your custom exception class to store extra information, such as error codes or detailed information about the exception.

// Custom exception class with additional fields
class InsufficientFundsException extends Exception {
    private double amount;

    public InsufficientFundsException(String message, double amount) {
        super(message);
        this.amount = amount;
    }

    public double getAmount() {
        return amount;
    }
}

public class BankAccount {
    private double balance;

    public BankAccount(double balance) {
        this.balance = balance;
    }

    // Method to withdraw money, throws custom exception if funds are insufficient
    public void withdraw(double amount) throws InsufficientFundsException {
        if (amount > balance) {
            throw new InsufficientFundsException("Insufficient funds for withdrawal", amount);
        } else {
            balance -= amount;
            System.out.println("Withdrawal successful. Remaining balance: " + balance);
        }
    }

    public static void main(String[] args) {
        BankAccount account = new BankAccount(1000.0);

        try {
            account.withdraw(1500.0);  // This will throw InsufficientFundsException
        } catch (InsufficientFundsException e) {
            System.out.println("Caught exception: " + e.getMessage());
            System.out.println("Attempted withdrawal amount: $" + e.getAmount());
        }
    }
}

Explanation:

InsufficientFundsException has an additional field (amount) to store the amount that caused the exception.
The withdraw() method checks if there are sufficient funds, and throws the custom exception with a message and the amount if there are insufficient funds.
The getAmount() method retrieves the amount that triggered the exception, which can be useful when handling the error.

Example 4: Creating a Custom Exception Hierarchy

You can create a hierarchy of custom exceptions for different types of errors in your application. This makes it easier to handle specific errors in different parts of your code.

// Base custom exception class
class ApplicationException extends Exception {
    public ApplicationException(String message) {
        super(message);
    }
}

// Subclass for a specific type of exception
class FileProcessingException extends ApplicationException {
    public FileProcessingException(String message) {
        super(message);
    }
}

// Subclass for another specific type of exception
class NetworkException extends ApplicationException {
    public NetworkException(String message) {
        super(message);
    }
}

public class ExceptionHierarchyExample {
    public static void main(String[] args) {
        try {
            simulateFileProcessingError();
        } catch (ApplicationException e) {
            System.out.println("Caught application exception: " + e.getMessage());
        }
    }

    public static void simulateFileProcessingError() throws FileProcessingException {
        throw new FileProcessingException("Error processing the file.");
    }
}

Explanation:

ApplicationException is the base custom exception class.
FileProcessingException and NetworkException are specific exceptions that extend the base class.
By creating an exception hierarchy, you can handle different types of errors more easily while maintaining a structured exception model.

6. Key Considerations

Meaningful Error Messages: Always provide meaningful error messages when throwing exceptions. This will help users and developers understand the cause of the error.
Checked vs. Unchecked Exceptions: Decide whether your custom exception should be a checked or unchecked exception. Use checked exceptions for recoverable conditions and unchecked exceptions for programming errors.
Custom Fields: You can add custom fields to your exception class to provide additional information about the error (e.g., error codes, invalid values, etc.).
Exception Hierarchy: If your application needs to handle various types of exceptions, consider creating an exception hierarchy to make error handling more organized and easier to maintain.
Exception Handling: Always handle exceptions appropriately. Even if you're throwing a custom exception, make sure you catch it and handle it in a way that provides meaningful feedback to the user.

Conclusion

In this tutorial, we explored how to create and use custom exceptions in Java. Custom exceptions are a powerful tool for handling application-specific errors and providing clear, meaningful error messages. We covered:

How to create a custom exception by extending the Exception or RuntimeException class.
Throwing and catching custom exceptions in your code.
The difference between checked and unchecked exceptions.
Examples of custom exceptions with additional fields and exception hierarchies.

Custom exceptions can greatly improve the clarity and robustness of your Java applications by helping you manage errors more effectively.

Related posts

Java throw keyword

Java finally block

Java Nested try Blocks tutorial with code examples