Exception chaining in Java
Wrapping causes with new X(msg, cause); getCause.
Translate, don't erase
When you catch a low-level exception and throw a higher-level one, pass the original as the cause: new DataException(msg, e). **getCause() returns the wrapped exception, and stack traces print it under Caused by:**.
try {
return repo.load(id);
} catch (SQLException e) {
throw new DataException("load " + id, e);
}Two messages
What does this print?
try {
try {
Integer.parseInt("12a");
} catch (NumberFormatException e) {
throw new RuntimeException("port", e);
}
} catch (RuntimeException e) {
System.out.println(e.getMessage());
System.out.println(e.getCause().getMessage());
}port For input string: "12a"port nullFor input string: "12a" port
Show the answer
The outer exception has its own message, port, and keeps the NumberFormatException as its cause — with the original message intact.
The Throwable toolkit
**getMessage() — the detail text. getCause() — the exception this one wraps, or null** if no cause was given (via constructor or initCause). **getSuppressed() — exceptions attached during cleanup. getStackTrace()** — the call-site frames as an array. Together they tell the whole story.
The missing , e
Forget to pass e and the new exception has no cause: the SQL details and their stack trace are thrown away. One argument makes all the difference.
} catch (SQLException e) {
throw new DataException("load " + id);
// fix: new DataException("load " + id, e)
}Down to the root
What does this print?
Throwable t = new RuntimeException("app",
new IllegalStateException("db",
new IOException("disk")));
while (t.getCause() != null) {
t = t.getCause();
}
System.out.println(t.getMessage());diskappdb
Show the answer
The loop walks the chain until an exception with no cause: the innermost IOException, whose message is disk.
Layered apps
A repository wraps SQLException in DataException; a service wraps that in OrderFailedException. Each layer speaks its own vocabulary, and the log still shows Caused by: all the way down to "connection refused" — so the on-call engineer knows exactly what broke.
Key takeaways
- new X(msg, cause), or initCause(cause)
- getCause() returns the wrapped Throwable, or null
- Translate low-level errors into domain errors without losing detail
- Stack traces print 'Caused by:' chains
💡 A doctor's referral letter that includes the original test results.
Exception chaining — getCause() and the cause constructors — only arrived in Java 1.4 (2002). Before that, each library invented its own way to wrap exceptions.
Practice questions
What does this print?
try {
try {
Integer.parseInt("abc");
} catch (NumberFormatException e) {
throw new IllegalStateException("config", e);
}
} catch (IllegalStateException e) {
System.out.println(e.getMessage());
System.out.println(e.getCause().getMessage());
}- config For input string: "abc"
- config null
- For input string: "abc" config
- Throws NullPointerException
Check your answer
config For input string: "abc". The outer exception has its own message, "config", and keeps the NumberFormatException as its cause — with the original message intact.
What does this print?
Throwable t = new RuntimeException("top",
new IllegalStateException("mid",
new ArithmeticException("root")));
while (t.getCause() != null) {
t = t.getCause();
}
System.out.println(t.getMessage());- root
- top
- mid
- null
Check your answer
root. The loop walks the chain until an exception with no cause: the innermost ArithmeticException, whose message is "root".