Custom Exceptions
Java's built-in exceptions cover general problems, but they rarely describe your application's specific business rules. A NumberFormatException tells you nothing about insufficient funds, an expired session, or a duplicate order — for that, you define your own exception class.
Extending Exception vs RuntimeException
A custom exception is created by extending either Exception (making it checked — callers must catch or declare it) or RuntimeException (making it unchecked — no compiler enforcement).
A checked custom exception
Java
public class InsufficientFundsException extends Exception {
public InsufficientFundsException(String message) {
super(message);
}
}An unchecked custom exception
Java
public class InvalidAccountStateException extends RuntimeException {
public InvalidAccountStateException(String message) {
super(message);
}
}In both cases, the constructor simply forwards the message string to the superclass constructor with super(message), which is what makes getMessage() return it later and what makes the message show up in a stack trace.
Choosing Checked vs Unchecked
Tip
Make a custom exception checked when it represents a condition the caller can reasonably be expected to recover from, and you want the compiler to force every caller to think about that possibility — think insufficient funds, a file not found, a failed network call. Make it unchecked when it represents a programming error or a violated invariant that callers shouldn't need to handle at every call site — think an invalid internal state or a precondition that should never fail if the code above is correct.
Scenario | Recommended Base | Reasoning |
Insufficient funds for a withdrawal | Exception (checked) | A recoverable, expected business condition — force callers to handle it |
An account object was used after being closed | RuntimeException (unchecked) | A programming bug — shouldn't happen if the code is correct |
Worked Example: A Banking Application
InsufficientFundsException.java
Java
public class InsufficientFundsException extends Exception {
private final double shortfall;
public InsufficientFundsException(String message, double shortfall) {
super(message);
this.shortfall = shortfall;
}
public double getShortfall() {
return shortfall;
}
}BankAccount.java
Java
public class BankAccount {
private double balance;
public BankAccount(double balance) {
this.balance = balance;
}
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
double shortfall = amount - balance;
throw new InsufficientFundsException(
"Cannot withdraw " + amount + ", balance is only " + balance,
shortfall);
}
balance -= amount;
}
public static void main(String[] args) {
BankAccount account = new BankAccount(100.0);
try {
account.withdraw(250.0);
} catch (InsufficientFundsException e) {
System.out.println(e.getMessage());
System.out.println("You are short by: " + e.getShortfall());
}
}
}Cannot withdraw 250.0, balance is only 100.0 You are short by: 150.0
Note
Because InsufficientFundsException extends the checked Exception, the compiler forced withdraw to declare throws InsufficientFundsException, which in turn forced main to either catch it or declare it too. Custom exception fields like shortfall above let you attach structured, business-specific data to the failure — something a generic built-in exception could never carry.
Tip
Naming convention: custom exception classes conventionally end in Exception (or Error for the rare unrecoverable case), making their purpose obvious at a glance wherever they're thrown or caught.