exceptions
beginnerCustom Exceptions & Chaining
Define domain-specific exception types and preserve the original cause when wrapping lower-level failures.
A custom exception is an exception class you write yourself, such as InsufficientFundsException, instead of reusing a general one like IllegalArgumentException.
A receipt that says "delivery failed — caused by: truck breakdown — caused by: engine fault". Each layer adds context without losing the root cause.
Key Concepts
1
Chaining means passing the original exception into the new one, so the log shows both what failed and
why.
2
Write one when a caller needs to tell it apart. If every caller treats your new exception exactly like
IllegalArgumentException, the extra class earns nothing. It pays when a caller can catch it
specifically — retry on AccountFrozenException, give up on InsufficientFundsException — or when it
carries data a message cannot.
IllegalArgumentExceptionAccountFrozenExceptionInsufficientFundsException
3
Extend RuntimeException unless you have a reason not to. Application errors usually pass through
several layers. A checked exception forces every one of them to declare or wrap it, and you end up with
throws lists nobody reads.
RuntimeExceptionthrows
4
Always pass the cause. This is the one that matters.
// the log says what failed, not why
catch (SQLException e) {
throw new DataAccessException("query failed");
}5
// the log shows the whole chain
catch (SQLException e) {
throw new DataAccessException("query failed for user " + id, e);
}6
With the cause, the log reads:
7
ServiceException: failed to load user 4821
Caused by: SQLException: connection pool exhausted
Caused by: SocketTimeoutException: connect timed out8
Three layers, one incident, and you know where to look. Drop the cause and only the first line survives.
9
Put details in fields, not in the message. If the handler has to pull an account number out of a string
with a regular expression, the design is wrong. Give the exception getAccountId() and getShortfall()
instead.
getAccountId()getShortfall()
10
Keep the hierarchy shallow. One base type per area — PaymentException — with two or three specific
ones under it. Callers can then catch broadly or precisely. Deep trees make people guess which class to
catch.
PaymentException
11
What the interviewer is probing.1. "When do you write one?" Probing: judgement. Stalls: "For every error." Moves up: when a
caller treats it differently, or when it carries data a message cannot.
12
2. "Checked or unchecked?" Probing: reasoning. Stalls: "Checked is safer." Moves up: unchecked
for errors crossing layers; checked when the immediate caller really can recover.
13
3. "What must every custom exception have?" Probing: the cause. Stalls: "A message." Moves up: a
(String, Throwable) constructor, and actually using it.
(String, Throwable)
14
4. "How does the handler get the details?" Probing: structure over strings. Stalls: "From the
message." Moves up: typed fields with getters, so nobody parses text.
15
5. "How would you make one cheap?" Probing: where the cost is. Stalls: "They are already cheap."
Moves up: capturing the stack trace is the expensive part; you can skip it for an expected,
high-frequency signal, and you lose the trace everywhere else.
16
A custom exception worth having17
public class InsufficientFundsException extends RuntimeException {
private final String accountId;
private final long shortfallMinor; // paise, not double18
public InsufficientFundsException(String accountId,
long shortfallMinor,
Throwable cause) {
super("Account " + accountId + " short by "
+ shortfallMinor, cause); // cause passed
this.accountId = accountId;
this.shortfallMinor = shortfallMinor;
}
public String getAccountId() { return accountId; }
public long getShortfallMinor(){ return shortfallMinor; }
}19
The four constructors to provide
(String message)
(String message, Throwable cause) <- the important one
(Throwable cause)
() optional20
Chained trace, what the log shows21
ServiceException: failed to load user 4821
at UserService.load(UserService.java:44)
Caused by: SQLException: connection pool exhausted
at HikariPool.getConnection(HikariPool.java:190)
Caused by: SocketTimeoutException: connect timed out
at ...22
-> three layers, one incident, immediately diagnosable.
Drop the cause and only the first line survives.23
Hot-path optimisation, used deliberately24
@Override
public synchronized Throwable fillInStackTrace() {
return this; // no stack capture: ~50x cheaper
}
// justified for an expected, high-frequency control signal;
// never for an exception that reports a real fault.