A custom exception is a type of exception that expresses a specific failure in the application’s domain.
Java has many ready-to-use exceptions, such as IOException, NullPointerException, or IllegalArgumentException.
But what if you are programming a banking system and a user tries to withdraw more money than they have?
You could throw an IllegalArgumentException, yes. But that is too generic. It doesn’t explain why the operation failed in your business context.
The ideal would be to throw an InsufficientBalanceException.
Creating your own exceptions allows your code to speak the language of your business, making errors easier to understand and manage.
How to Create an Exception
Technically, creating an exception is ridiculously easy. You just need to create a class and make it inherit from one of the parent classes.
// Option A: Checked Exception (Forces handling)
public class InsufficientBalanceException extends Exception {
public InsufficientBalanceException(String message) {
super(message); // Pass the message to the parent (Exception)
}
}That’s it. Now you can use it in your code:
throw new InsufficientBalanceException("You are short by 50 euros");Choosing Between Exception and RuntimeException
When creating an exception, you must decide whether the caller will be forced to declare or catch it:
Extending Exception (checked)
You choose this option when the error is a foreseeable business event from which we can recover. You want to force the programmer using your method to think about what to do if this happens.
-
Example:
InsufficientBalanceException. -
Why: It is very likely that a user will try to withdraw too much money. If it happens, we want to force the graphical interface to display a nice message: “Insufficient balance, try a smaller amount.”
Extending RuntimeException (unchecked)
You choose this option when the error indicates a bug, incorrect API usage, or an unrecoverable state. You do not want to force a try-catch because if this happens, the programmer should fix their code, not catch the error.
-
Example:
AccountBlockedException(if it’s a fatal state) orNegativeAmountException. -
Why: If someone tries to deposit
-100euros, it’s a logic failure. We don’t want a try-catch; we want the programmer to fix that call.
Complete Example: The ATM
Let’s implement the bank case using a checked exception because we want to handle it.
Step 1: The Exception We can add extra attributes to provide more information (e.g., how much money is missing).
public class InsufficientBalanceException extends Exception {
private double missingAmount;
public InsufficientBalanceException(String message, double missing) {
super(message);
this.missingAmount = missing;
}
public double getMissingAmount() {
return missingAmount;
}
}Step 2: The Business Class
public class BankAccount {
private double balance = 100.0;
// We WARN with 'throws' that this method can fail
public void withdraw(double amount) throws InsufficientBalanceException {
if (amount > balance) {
double missing = amount - balance;
// We THROW the error
throw new InsufficientBalanceException("There isn't enough money", missing);
}
balance -= amount;
System.out.println("Successful withdrawal. Remaining: " + balance);
}
}Step 3: The Usage (Handling)
public static void main(String[] args) {
BankAccount account = new BankAccount();
try {
account.withdraw(500.0); // This will fail
} catch (InsufficientBalanceException e) {
// We handle the business error elegantly
System.out.println("Error: " + e.getMessage());
System.out.println("You are short by: " + e.getMissingAmount() + " euros.");
}
}