76. Custom Exceptions
Create custom exceptions by extending Exception (checked) or RuntimeException (unchecked); always provide a message constructor and a cause-preserving constructor; keep exception class names clear and domain-specific.
Explanation
Custom exceptions are worth creating when: standard exceptions don't convey enough domain context, you want callers to be able to catch a specific failure class without catching overly broad types, or you need to attach extra state (error code, resource ID, etc.).
A well-written custom exception provides at least: MyException(String message) and MyException(String message, Throwable cause). The cause constructor enables proper exception chaining โ always wrap the original exception as the cause rather than swallowing it. Also provide MyException(Throwable cause) for translation patterns.
Choose checked vs unchecked: checked if callers can and should handle the condition; unchecked (RuntimeException subclass) for programming errors or conditions that propagate through many layers. Effective Java recommends erring on the side of unchecked.
Example
// Custom checked exception with domain context
class InsufficientFundsException extends Exception {
private final double amount;
private final double balance;
public InsufficientFundsException(double amount, double balance) {
super(String.format("Cannot withdraw %.2f: balance is only %.2f", amount, balance));
this.amount = amount;
this.balance = balance;
}
// Cause-preserving constructor
public InsufficientFundsException(double amount, double balance, Throwable cause) {
super(String.format("Cannot withdraw %.2f: balance is only %.2f", amount, balance), cause);
this.amount = amount;
this.balance = balance;
}
public double getAmount() { return amount; }
public double getBalance() { return balance; }
}
// Custom unchecked exception
class ConfigurationException extends RuntimeException {
private final String key;
public ConfigurationException(String key) {
super("Missing required configuration: " + key);
this.key = key;
}
public ConfigurationException(String key, Throwable cause) {
super("Missing required configuration: " + key, cause);
this.key = key;
}
public String getKey() { return key; }
}
public class CustomExceptions {
static class BankAccount {
private double balance;
BankAccount(double balance) { this.balance = balance; }
void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) throw new InsufficientFundsException(amount, balance);
balance -= amount;
}
double getBalance() { return balance; }
}
public static void main(String[] args) {
BankAccount account = new BankAccount(100.00);
try {
account.withdraw(150.00);
} catch (InsufficientFundsException e) {
System.out.println("Error: " + e.getMessage());
System.out.printf("Tried to withdraw: %.2f, balance: %.2f%n",
e.getAmount(), e.getBalance());
}
// Unchecked: no try-catch required
try {
throw new ConfigurationException("database.url");
} catch (ConfigurationException e) {
System.out.println("Config error: " + e.getMessage());
System.out.println("Missing key: " + e.getKey());
}
}
}Sources
- https://docs.oracle.com/javase/tutorial/essential/exceptions/creating.html โ Creating custom exceptions
- https://www.pearson.com/en-us/subject-catalog/p/effective-java/P200000000138 โ Effective Java, Item 72