Custom Exceptions in Java
IllegalArgumentException says "something was wrong with an argument". InsufficientFundsException says exactly what went wrong in a bank, and callers can catch that problem specifically, without catching unrelated ones by accident. A custom exception is simply a class that extends Exception (for a checked exception) or RuntimeException (for an unchecked one). Writing a good one takes a few lines and makes your code's failures self-explanatory.
A Minimal Custom Exception
public class Bank {
static class InsufficientFundsException extends Exception {
private final long shortBy;
InsufficientFundsException(long shortBy) {
super("Short by Rs. " + shortBy);
this.shortBy = shortBy;
}
long shortBy() { return shortBy; }
}
static long balance = 1500;
static void withdraw(long amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(amount - balance);
}
balance -= amount;
}
public static void main(String[] args) {
try {
withdraw(2000);
} catch (InsufficientFundsException e) {
System.out.println(e.getMessage());
System.out.println("Ask Srihitha to add Rs. " + e.shortBy());
}
}
}Short by Rs. 500
Ask Srihitha to add Rs. 500How it works
extends Exceptionmakes it a checked exception, sowithdrawmust declare it.super("Short by Rs. " + shortBy)sets the message thatgetMessage()returns.- The extra field
shortBycarries useful data, so the caller can act on it without parsing the message.
Checked or Unchecked?
Make it checked (extends Exception) when... | Make it unchecked (extends RuntimeException) when... |
|---|---|
| The caller can reasonably recover: not enough money, file not found, user not registered | It signals a bug or broken rule the caller can't fix at runtime |
| You want the compiler to remind every caller | Forcing a try everywhere would just add noise |
Many modern codebases lean toward unchecked custom exceptions and document them; both styles are valid when used consistently.
The Constructors to Provide
A well-behaved exception offers the same constructors as Java's own, so it can be created with or without a message and a cause:
public class PaymentException extends Exception {
public PaymentException(String message) {
super(message);
}
public PaymentException(String message, Throwable cause) {
super(message, cause);
}
}The (message, cause) constructor is the important one: it lets you wrap a lower-level exception without losing it (see "Chained Exceptions in Java").
Putting It Together: A Number-Parsing Utility
This chapter's checkpoint combines a custom checked exception, a multi-catch and a chained cause:
public class NumberTools {
static class InvalidAmountException extends Exception {
InvalidAmountException(String input, Throwable cause) {
super("Not a valid amount: " + input, cause);
}
}
static long parseAmount(String text) throws InvalidAmountException {
long value;
try {
value = Long.parseLong(text.strip().replace(",", ""));
} catch (NumberFormatException | NullPointerException e) {
throw new InvalidAmountException(String.valueOf(text), e);
}
if (value < 0) {
throw new InvalidAmountException(text, null);
}
return value;
}
public static void main(String[] args) {
String[] inputs = {"1,200", " 450 ", "12a", null, "-5"};
for (String in : inputs) {
try {
System.out.println(in + " -> " + parseAmount(in));
} catch (InvalidAmountException e) {
Throwable cause = e.getCause();
String why = cause == null
? "none" : cause.getClass().getSimpleName();
System.out.println(e.getMessage() + " (cause: " + why + ")");
}
}
}
}1,200 -> 1200
450 -> 450
Not a valid amount: 12a (cause: NumberFormatException)
Not a valid amount: null (cause: NullPointerException)
Not a valid amount: -5 (cause: none)How it works
strip()andreplace(",", "")clean the input, so"1,200"and" 450 "parse correctly.- Multi-catch handles both ways parsing can fail: bad text (
NumberFormatException) and no text (NullPointerExceptionfrom callingstrip()onnull). - Each failure is wrapped in
InvalidAmountExceptionwith the original as its cause. - A negative number parses fine but breaks a rule, so it's thrown with no cause.
- Callers deal with one meaningful exception type and can still inspect the cause.
Common Mistakes
Extending Throwable or Error. Extend Exception or RuntimeException.
No message or cause constructors. Callers can't explain or wrap failures properly.
Catching your custom exception and throwing a new one without the cause. Pass it along as the cause.
Interview Corner
How do you create a custom exception in Java?
Define a class that extends Exception for a checked exception or RuntimeException for an unchecked one, and provide constructors that pass a message, and optionally a cause, to super. Add fields for any extra data callers need. Name it after the problem, ending in "Exception", and throw it with throw new MyException(...).
Should a custom exception be checked or unchecked?
Make it checked if callers can reasonably recover and should be forced to consider the case, such as insufficient funds. Make it unchecked if it represents a programming error or a condition callers usually can't handle at that point, to avoid cluttering every call site with try-catch.
Practice
- Easy: Create an unchecked
InvalidMarksExceptionand throw it fromsetMarks(int marks)for values outside 0 to 100. - Harder: Extend
NumberToolswithparsePercent(String text)that reusesparseAmountand throwsInvalidAmountExceptionfor values above 100, with no cause.
Key Takeaways
Was this tutorial helpful?