💥 Exceptions & Errors · Intermediate

Suppressed exceptions in Java

Exceptions from close() attached via getSuppressed().

🧩 The mysteryYour code fails, then the cleanup fails too. Two exceptions at once — but Java can only throw one. Which survives, and what happens to the other?

Two failures at once

In try-with-resources, if the body throws and then close() throws too, the body's exception stays primary — it's the one that propagates. The close() failure is attached to it as a suppressed exception via addSuppressed(), so nothing is lost.

🔮 Predict it

Count the suppressed

Both the body and close() throw. What prints?

AutoCloseable res = () -> {
    throw new IllegalStateException("close");
};
try (res) {
    throw new RuntimeException("body");
} catch (Exception e) {
    System.out.println(e.getMessage());
    System.out.println(e.getSuppressed().length);
}
  1. body 1
  2. close 1
  3. body 0
Show the answer

The body's exception is primary, so its message is body. The close() failure sits in its suppressed array, which has length 1.

Reading them

**e.getSuppressed()** returns a Throwable[] with everything attached. Printing one shows its class and message, like java.io.IOException: C. Stack traces list them under **Suppressed:. And addSuppressed(Throwable)** is public (Java 7+), so your own cleanup code can attach them too.

catch (Exception e) {
    for (Throwable s : e.getSuppressed())
        System.out.println(s);
}
⚠️ The trap

Plain finally loses the original

With an ordinary try/finally, an exception thrown in finally simply replaces the one in flight. Here the catch sees only cleanup — original is gone forever. That's exactly the problem suppressed exceptions solve.

try {
    try {
        throw new RuntimeException("original");
    } finally {
        throw new IllegalStateException("cleanup");
    }
} catch (RuntimeException e) {
    System.out.println(e.getMessage()); // cleanup
}
🤔 Think first

Why the body wins

Why does Java keep the body's exception as primary rather than the one from close()?

Think about it, then reveal the answer

The body's exception is usually the real reason things went wrong; a failing close() is often just a consequence. Keeping it primary — with the close() failure attached — tells the whole story in the right order.

💼 In the real world

Debugging with the full story

Before Java 7, a database error was often hidden behind a "connection reset" thrown while closing — engineers chased the wrong bug for hours. Today, scroll down to Suppressed: in the log and both failures are there.

Key takeaways

  1. Primary exception = the one thrown by the try body
  2. close() failures are attached with addSuppressed()
  3. Read them with e.getSuppressed() (an array)
  4. Stack traces list them under 'Suppressed:'
🤯 Did you know?

Throwable has a protected constructor with an enableSuppression flag. Pass false and addSuppressed quietly does nothing.

Practice questions

What does this print?

AutoCloseable res = () -> { throw new IOException("C"); };
try (res) {
    throw new RuntimeException("B");
} catch (Exception e) {
    System.out.println(e.getMessage());
    System.out.println(e.getSuppressed()[0]);
}
  1. B java.io.IOException: C
  2. C java.lang.RuntimeException: B
  3. B null
  4. Throws IOException
Check your answer

B java.io.IOException: C. The body's RuntimeException is primary. The IOException from close() is stored in its suppressed array.

How do you read the exceptions that were suppressed during cleanup?

  1. Call getSuppressed() on the primary exception — it returns an array
  2. Call getCause() repeatedly
  3. They go only to System.err and can't be read
  4. Add a second catch block for them
Check your answer

Call getSuppressed() on the primary exception — it returns an array. getSuppressed() returns a Throwable[] with everything attached via addSuppressed(), which try-with-resources does automatically.

Next: wrapping exceptions inside exceptions, like Russian dolls — without losing the one at the core.