Designing Your Own Exceptions
When a new exception type earns its keep, what to put on it, and why the cause must survive.
Open this lesson in the learning hubKey points
- Extend
RuntimeExceptionfor programming errors and bad input; extendExceptiononly when a caller can recover. - Add a type per recoverable outcome, not per message. If callers cannot act differently, reuse an existing type.
- Give it fields, not just text: an
orderIdon the exception beats parsing it back out of a message. - Always pass the cause with
super(message, cause). A trace with the original failure missing is a lost afternoon. catchclauses are tried in the order you wrote them, so a specific type must be listed above its supertype.- One small family under a shared base type lets a caller catch the family or one member, whichever it can handle.
Example
public class Main {
// One base type for the family, so a caller can catch broadly or narrowly.
static class PaymentException extends RuntimeException {
private final String orderId;
PaymentException(String orderId, String message, Throwable cause) {
super(message, cause); // never drop the cause
this.orderId = orderId;
}
String orderId() { return orderId; }
}
static final class NotEnoughFunds extends PaymentException {
private final int shortfall;
NotEnoughFunds(String orderId, int shortfall) {
super(orderId, "short by " + shortfall + " cents", null);
this.shortfall = shortfall; // data, not just a message
}
int shortfall() { return shortfall; }
}
static void charge(String orderId, int balance, int price) {
if (price > balance) throw new NotEnoughFunds(orderId, price - balance);
System.out.println("charged " + price + " for " + orderId);
}
public static void main(String[] args) {
charge("A-1", 5000, 4200);
try {
charge("A-2", 100, 4200);
} catch (NotEnoughFunds e) { // the specific type comes first
System.out.println("top up " + e.shortfall() + " cents on " + e.orderId());
} catch (PaymentException e) {
System.out.println("payment failed on " + e.orderId());
}
try {
throw new PaymentException("A-3", "gateway timed out", null);
} catch (NotEnoughFunds e) { // wrong subtype, skipped
System.out.println("top up " + e.shortfall());
} catch (PaymentException e) { // this one matches
System.out.println("payment failed on " + e.orderId() + ": " + e.getMessage());
} catch (RuntimeException e) { // never reached
System.out.println("something else");
}
try {
try {
Integer.parseInt("not-a-number");
} catch (NumberFormatException cause) {
throw new PaymentException("A-4", "bad amount on the request", cause);
}
} catch (PaymentException e) {
System.out.println(e.getMessage() + " <- " + e.getCause().getClass().getSimpleName());
}
}
}
A new exception type is for a caller who will act on it, and it must carry the cause.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the OOP course, and every lesson in it is listed on the OOP contents page.