Custom Exceptions

⭐ Interview Importance: MEDIUM
⏱️ Revision Time: 5 min

TL;DR

  • You can create your own exception classes to represent specific business logic errors.
  • To create a Checked Custom Exception, extend the Exception class.
  • To create an Unchecked Custom Exception, extend the RuntimeException class.

Concept

While Java provides many built-in exceptions (IllegalArgumentException, NullPointerException), they are often too generic to convey exactly what went wrong in your domain model.

If an ATM application tries to withdraw money from an empty account, throwing an IllegalStateException works, but throwing an InsufficientFundsException is much clearer. It makes the code more readable and allows callers to write specific catch blocks just for banking errors.

Examples

// 1. Create a custom Unchecked Exception
class InsufficientFundsException extends RuntimeException {
    
    // Provide a constructor that accepts an error message
    public InsufficientFundsException(String message) {
        super(message); // Pass it to the parent RuntimeException class
    }
}

class BankAccount {
    private double balance;
    
    public BankAccount(double balance) { this.balance = balance; }
    
    public void withdraw(double amount) {
        if (amount > balance) {
            // 2. Throw your custom exception
            throw new InsufficientFundsException("Cannot withdraw $" + amount + ". Balance is $" + balance);
        }
        balance -= amount;
        System.out.println("Withdrew $" + amount);
    }
}

public class CustomExceptionExample {
    public static void main(String[] args) {
        BankAccount account = new BankAccount(100);
        
        try {
            account.withdraw(500);
        } catch (InsufficientFundsException e) {
            // 3. Catch your custom exception
            System.err.println("Transaction Failed: " + e.getMessage());
        }
    }
}

Interview Questions

Q: How do you decide whether a custom exception should extend Exception or RuntimeException?
A: If you want to force the caller of your method to explicitly handle the error (because the error is recoverable or expected, like a missing file or a failed network connection), extend Exception (Checked).
If the error represents a programming bug or an unrecoverable state (like invalid input data, or a missing Spring Bean), extend RuntimeException (Unchecked). In modern Java, extending RuntimeException is the standard practice for business logic.

Q: Why is it good practice to provide Throwable cause in custom exception constructors?
A: Often, your custom exception is triggered by an underlying Java exception (e.g., an IOException causes your DatabaseConnectionException). By providing a constructor like public MyException(String msg, Throwable cause) and passing it to super(msg, cause), you preserve the original stack trace. If you don’t do this, you lose the root cause of the error, making debugging extremely difficult.