Define your own exception types when callers need to react to a specific failure — and always keep the original cause attached.
Defining one
public class Main {
public static void main(String[] args) {
Account acc = new Account(100);
try {
acc.withdraw(30);
acc.withdraw(500);
} catch (InsufficientFundsException e) {
System.out.println(e.getMessage());
System.out.println("short by " + e.shortfall());
}
}
}
// Checked: the caller can reasonably recover (show a message, offer a smaller amount)
class InsufficientFundsException extends Exception {
private final long shortfall;
InsufficientFundsException(long balance, long requested) {
super("balance " + balance + " < requested " + requested);
this.shortfall = requested - balance;
}
long shortfall() { return shortfall; }
}
class Account {
private long balance;
Account(long balance) { this.balance = balance; }
void withdraw(long amount) throws InsufficientFundsException {
if (amount <= 0) throw new IllegalArgumentException("amount must be positive"); // bug → unchecked
if (amount > balance) throw new InsufficientFundsException(balance, amount);
balance -= amount;
}
}balance 70 < requested 500
short by 430Checked or unchecked?
| Choose | When | Extend |
|---|---|---|
| Checked | The caller can and should recover: file missing, payment declined, validation of external input | Exception |
| Unchecked | Programming errors / violated preconditions, or failures nobody nearby can handle | RuntimeException |
Modern code (Spring, most libraries, lambdas and streams) leans unchecked: checked exceptions don't compose with functional interfaces like Function, and they leak implementation details up the call chain.
Exception chaining: keep the cause
public class Main {
static class ConfigException extends RuntimeException {
ConfigException(String msg, Throwable cause) { super(msg, cause); }
}
static int loadPort(String raw) {
try {
return Integer.parseInt(raw);
} catch (NumberFormatException e) {
throw new ConfigException("invalid port in config: " + raw, e); // wrap, keep cause
}
}
public static void main(String[] args) {
try {
loadPort("80a");
} catch (ConfigException e) {
System.out.println(e.getMessage());
System.out.println("caused by " + e.getCause().getClass().getSimpleName()
+ ": " + e.getCause().getMessage());
}
}
}invalid port in config: 80a
caused by NumberFormatException: For input string: "80a"Translate low-level exceptions into ones that match your layer's abstraction (a repository throws DataAccessException, not SQLException) — but pass the original as the cause so the stack trace still shows the root problem (Caused by: …).
Anti-patterns
| Anti-pattern | Why it hurts | Instead |
|---|---|---|
catch (Exception e) {} | Swallows the failure — the bug disappears silently | Handle it, or let it propagate |
e.printStackTrace() and continue | Output goes to stderr, program runs on with bad state | Log with a logger and rethrow/handle |
throw new RuntimeException(e.getMessage()) | Loses the original type and stack trace | throw new XException("context", e) |
| Catch-log-rethrow at every layer | The same error is logged five times | Log once, where it's handled |
| Exceptions for control flow | Slow (stack trace capture) and hides intent | Use if, Optional, return values |
throws Exception on every method | Callers can't tell what can go wrong | Declare specific types |
Catching NullPointerException | Masks a bug | Fix the null source / validate early |
Best practices
Fail fast: validate arguments at the start of a method (
Objects.requireNonNull,IllegalArgumentException).Use standard exceptions when they fit:
IllegalArgumentException,IllegalStateException,UnsupportedOperationException,NoSuchElementException.Put useful context in the message: the bad value, the id, the expected range.
Name custom types
…Exceptionand provide(String message)and(String message, Throwable cause)constructors.Restore the interrupt flag if you catch
InterruptedExceptionand can't rethrow it:Thread.currentThread().interrupt();.Clean up with try-with-resources, not
finallyblocks.
How do you create a custom exception?
What is exception chaining and why use it?
Why are exceptions expensive?