Exception Handling

Learn how to handle exceptions gracefully using try-catch blocks. Understand different exception types and best practices for error handling.


Custom Exceptions in Java

What are Custom Exceptions?

In Java, exceptions are used to handle runtime errors. Java provides a wide range of built-in exception classes (like NullPointerException, IOException, etc.). However, sometimes you need to represent errors specific to your application's domain. That's where custom exceptions come in.

Custom exceptions allow you to define your own exception classes, tailored to the specific errors that might occur within your application. This makes your code more readable, maintainable, and robust because you can handle errors in a more meaningful way.

Why Use Custom Exceptions?

  • Clarity: They provide more descriptive error messages related to your application's logic.
  • Specific Handling: They allow you to handle specific types of errors differently. You can catch and respond to your custom exceptions in a way that makes sense for the situation.
  • Organization: They improve the overall organization and readability of your code by separating error handling logic from the main application logic.
  • Maintainability: When requirements change, your custom exceptions can be updated to reflect new error scenarios.

Creating Custom Exception Classes

To create a custom exception class in Java, you need to:

  1. Extend the Exception or RuntimeException class.
    • Extending Exception creates a checked exception, which the calling method must either catch or declare in its throws clause.
    • Extending RuntimeException creates an unchecked exception. These are not required to be caught or declared.
  2. Optionally, define custom constructors to initialize the exception with relevant information (e.g., an error message).
  3. Optionally, add additional fields or methods to store and retrieve information about the error.

Here's a basic example:

 // Custom checked exception example
class InsufficientFundsException extends Exception {
    public InsufficientFundsException(String message) {
        super(message); // Call the superclass constructor
    }
}

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


public class BankAccount {
    private double balance;

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

    public void withdraw(double amount) throws InsufficientFundsException {
        if (amount > balance) {
            throw new InsufficientFundsException("Insufficient funds.  Withdrawal amount: " + amount + ", Balance: " + balance);
        }
        balance -= amount;
        System.out.println("Withdrawal successful.  New balance: " + balance);
    }

    public void deposit(double amount) {
        if (amount <= 0) {
            throw new InvalidInputException("Deposit amount must be positive.");
        }
        balance += amount;
        System.out.println("Deposit successful.  New balance: " + balance);
    }

    public double getBalance() {
        return balance;
    }

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

        try {
            account.withdraw(150.0);  // This will throw InsufficientFundsException
        } catch (InsufficientFundsException e) {
            System.err.println("Error: " + e.getMessage());
        }

        try{
            account.deposit(-10);
        } catch (InvalidInputException e) {
            System.err.println("Error: " + e.getMessage());
        }


        System.out.println("Current Balance: " + account.getBalance());
    }
} 

Explanation:

  • InsufficientFundsException extends Exception, making it a checked exception. This means that any method that calls withdraw must handle this exception using a try-catch block or declare it in its throws clause.
  • InvalidInputException extends RuntimeException making it an unchecked exception. It is not mandatory to catch, but good practice often means you still implement some kind of error handling.
  • Both exceptions include a constructor that takes a message to provide more context about the error. This message can then be accessed when the exception is caught.
  • The BankAccount class demonstrates how to throw and catch these custom exceptions.

Handling Custom Exceptions

Custom exceptions are handled just like any other exception in Java, using try-catch blocks:

 try {
    // Code that might throw the custom exception
    // For example:
    // account.withdraw(1000.0);
} catch (InsufficientFundsException e) {
    // Handle the exception
    System.err.println("Error: " + e.getMessage());
    // Perform other actions, like logging, retrying, or displaying an error message to the user.
} 

When handling exceptions, remember to catch exceptions in a specific-to-general order. This means catching more specific exception types before more general ones (e.g., catching InsufficientFundsException before catching Exception). If you catch Exception first, the catch block for InsufficientFundsException will never be executed.

Best Practices for Custom Exceptions

  • Choose the right base class. Decide whether your exception should be checked (extend Exception) or unchecked (extend RuntimeException) based on whether the calling code can reasonably be expected to recover from the error. Checked exceptions force the calling code to handle the error, while unchecked exceptions are usually used for programming errors or situations where recovery is impossible.
  • Provide informative error messages. The error message should clearly explain what went wrong and, if possible, suggest how to fix the problem.
  • Document your exceptions. Use Javadoc to document your custom exception classes and the conditions under which they are thrown.
  • Don't overuse custom exceptions. Only create custom exceptions when the built-in exception classes are not sufficient to represent the specific error conditions in your application.
  • Consider using exception chaining. If one exception causes another, you can chain them together to provide more context about the root cause of the error. Use the initCause() method of the Throwable class.