Chained Exceptions in Java
A low-level error like NumberFormatException: For input string: "9O" is precise but says nothing about what the program was doing. A higher-level message like "Bad marks for Advitha" explains the situation but hides the technical detail. Chained exceptions give you both: you throw a new, meaningful exception and attach the original one as its cause. Nothing is lost, and each level of the program speaks its own language.
Wrapping with a Cause
public class Chain {
static class ReportException extends Exception {
ReportException(String message, Throwable cause) {
super(message, cause);
}
}
static int parseMarks(String student, String text) throws ReportException {
try {
return Integer.parseInt(text);
} catch (NumberFormatException e) {
String msg = "Bad marks for " + student + ": " + text;
throw new ReportException(msg, e);
}
}
public static void main(String[] args) {
try {
parseMarks("Advitha", "9O");
} catch (ReportException e) {
System.out.println("Problem: " + e.getMessage());
System.out.println("Cause: " + e.getCause());
}
}
}Problem: Bad marks for Advitha: 9O
Cause: java.lang.NumberFormatException: For input string: "9O"How it works
ReportExceptionpasses both a message and a cause to its parent's constructor,super(message, cause). Every standard exception class offers this constructor.- In the
catch, the originalNumberFormatExceptioneis passed as the cause of the new exception. - The caller sees the meaningful message, and
getCause()returns the original exception, with its own message and stack trace. - If it went uncaught, the stack trace would show the original under
Caused by:(see "Reading a Stack Trace in Java").
Why Wrap at All?
| Reason | Example |
|---|---|
| Speak the caller's language | A PaymentFailedException means more to the checkout code than a SocketTimeoutException |
| Hide implementation details | Switching from files to a database shouldn't change the exceptions callers see |
| Turn checked into unchecked | Wrap an IOException in UncheckedIOException inside a lambda, which can't throw checked exceptions |
The last case is very common:
import java.io.IOException;
import java.io.UncheckedIOException;
public class Unchecked {
static String readSettings() throws IOException {
throw new IOException("settings.txt not readable");
}
static String settingsOrFail() {
try {
return readSettings();
} catch (IOException e) {
throw new UncheckedIOException("Could not load settings", e);
}
}
public static void main(String[] args) {
try {
settingsOrFail();
} catch (UncheckedIOException e) {
System.out.println(e.getMessage() + " <- "
+ e.getCause().getMessage());
}
}
}Could not load settings <- settings.txt not readableCommon Mistakes
Wrapping without the cause. throw new ReportException("Bad marks") loses the original stack trace forever.
Wrapping just to wrap. If the new exception adds no meaning, let the original propagate.
Copying the cause's message into the new one and dropping the cause. Pass the cause object instead.
Interview Corner
What are chained exceptions?
Chaining lets one exception record another as its cause. It's done by passing the original exception to a constructor that accepts a Throwable cause, or by calling initCause, and the cause is retrieved with getCause(). Stack traces show the chain under "Caused by". It's used to translate low-level exceptions into higher-level ones, or checked into unchecked ones, without losing the original information.
Practice
- Easy: Remove
, efrom thethrowinChain.java. What doesgetCause()print now? - Harder: Build a three-level chain:
NumberFormatExceptionwrapped inReportException, wrapped inIllegalStateException. Then write a loop that prints the root cause.
Key Takeaways
Was this tutorial helpful?