Chained Exceptions in Java

Naveen AnamaneniUpdated · 4 min read

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

The outer exception explains; the cause keeps the original detail
Chain.java▶ Try it
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());
        }
    }
}
Output
Problem: Bad marks for Advitha: 9O
Cause:   java.lang.NumberFormatException: For input string: "9O"

How it works

  1. ReportException passes both a message and a cause to its parent's constructor, super(message, cause). Every standard exception class offers this constructor.
  2. In the catch, the original NumberFormatException e is passed as the cause of the new exception.
  3. The caller sees the meaningful message, and getCause() returns the original exception, with its own message and stack trace.
  4. 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?

ReasonExample
Speak the caller's languageA PaymentFailedException means more to the checkout code than a SocketTimeoutException
Hide implementation detailsSwitching from files to a database shouldn't change the exceptions callers see
Turn checked into uncheckedWrap an IOException in UncheckedIOException inside a lambda, which can't throw checked exceptions

The last case is very common:

Unchecked.java▶ Try it
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());
        }
    }
}
Output
Could not load settings <- settings.txt not readable

Common 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

  1. Easy: Remove , e from the throw in Chain.java. What does getCause() print now?
  2. Harder: Build a three-level chain: NumberFormatException wrapped in ReportException, wrapped in IllegalStateException. Then write a loop that prints the root cause.

Quick check: Chained exceptions

4 questions
1How do you attach the original exception?
2Which method returns the cause?
3Where does a stack trace show the cause?
4Why wrap an IOException in UncheckedIOException?

Key Takeaways

Was this tutorial helpful?

Lesson 231 of 482