Java Custom Exceptions
Java ships many exceptions, such as ArithmeticException and NullPointerException. Real applications need errors that match their own rules. A custom exception is a class you write to describe one specific problem in your program.
An ATM Example
An ATM refuses a withdrawal when the balance is too low. The message "Not enough money" tells the customer exactly what happened. A custom exception named InsufficientBalanceException carries that meaning through the code and keeps error handling clear.
Diagram: Exception Family Tree
Throwable
|
+-- Error
|
+-- Exception ---------------- checked exceptions
|
+-- RuntimeException --- unchecked exceptions
|
+-- InsufficientBalanceException (your class)
Checked and Unchecked Custom Exceptions
| Type | Extends | Compiler Rule |
|---|---|---|
| Checked | Exception | Callers must catch it or declare it |
| Unchecked | RuntimeException | Callers may ignore it |
Choose a checked exception when the caller can recover, such as asking the user to retry. Choose an unchecked exception for programming mistakes, such as a wrong argument.
Creating a Custom Exception
class InsufficientBalanceException extends Exception {
public InsufficientBalanceException(String message) {
super(message);
}
}
The constructor passes the message to the parent class. The getMessage method returns it later.
Throwing the Exception
Use throw to raise the exception and throws in the method header to warn callers.
class Account {
private double balance = 1000;
void withdraw(double amount) throws InsufficientBalanceException {
if (amount > balance) {
throw new InsufficientBalanceException(
"Balance is " + balance + ", requested " + amount);
}
balance -= amount;
}
}
Catching the Exception
public class Main {
public static void main(String[] args) {
Account acc = new Account();
try {
acc.withdraw(1500);
} catch (InsufficientBalanceException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
Output:
Error: Balance is 1000.0, requested 1500.0
Diagram: Flow of the Error
main() --> withdraw(1500)
|
amount > balance ?
yes |
v
throw InsufficientBalanceException
|
v
catch block in main() prints message
Unchecked Version
class InvalidAgeException extends RuntimeException {
public InvalidAgeException(String message) {
super(message);
}
}
void setAge(int age) {
if (age < 0) {
throw new InvalidAgeException("Age cannot be negative");
}
}
Keeping the Original Cause
Add a second constructor that accepts a cause. The new exception carries the story of the first one.
class DataLoadException extends Exception {
public DataLoadException(String message, Throwable cause) {
super(message, cause);
}
}
Good Habits
- End the class name with Exception
- Write clear messages with real numbers or names
- Reuse built-in exceptions when they fit, such as IllegalArgumentException
- Create few custom exceptions, one for each real business rule
Common Mistakes
- Confusing throw with throws
- Catching the general Exception class and hiding the real problem
- Leaving the message empty
Real Example: Age Check at Registration
A sign-up form for a library card accepts members from age 5 to 100. A custom exception reports an age outside this range. The form catches the exception and shows a friendly message beside the field.
class InvalidAgeException extends Exception {
InvalidAgeException(String msg) {
super(msg);
}
}
class Registration {
void register(int age) throws InvalidAgeException {
if (age < 5 || age > 100) {
throw new InvalidAgeException("Age must be 5 to 100");
}
System.out.println("Member added");
}
}
The register method states its rule in one place. Every screen that calls the method reuses the same rule and the same message, so users see consistent feedback.
Quick Summary
- Extend Exception for checked errors
- Extend RuntimeException for unchecked errors
- throw raises the error and throws announces it
