Suppressed Exceptions in Java
Picture this: your code throws an exception while writing to a file, and then, while Java is closing that file, close() throws another exception. Which one should the caller see? The first one is the real problem; the second is a side effect. With an old-style finally, the second exception replaces the first, and the real cause is lost. Try-with-resources keeps the first as the main exception and attaches the others to it as suppressed exceptions.
Two Failures, Nothing Lost
public class Suppressed {
static class Resource implements AutoCloseable {
private final String name;
Resource(String name) { this.name = name; }
void use() {
throw new IllegalStateException("use failed: " + name);
}
@Override
public void close() {
throw new IllegalArgumentException("close failed: " + name);
}
}
public static void main(String[] args) {
try (Resource a = new Resource("A");
Resource b = new Resource("B")) {
b.use();
} catch (Exception e) {
System.out.println("main: " + e.getMessage());
for (Throwable s : e.getSuppressed()) {
System.out.println("suppressed: " + s.getMessage());
}
}
}
}main: use failed: B
suppressed: close failed: B
suppressed: close failed: AHow it works
b.use()throws: this becomes the main exception.- Java closes
B, thenA. Bothclose()calls throw too. - Instead of replacing the main exception, each closing exception is added to it with
addSuppressed. - The caller catches the main exception, the real problem, and can still see the others with
getSuppressed(). - Printed with
printStackTrace(), the suppressed ones appear under aSuppressed:heading.
Why This Matters
Without suppression, debugging becomes guesswork: the stack trace says "close failed", which is a symptom, and the real failure has vanished. With suppression, the main exception tells you what went wrong first, and the suppressed list tells you what else broke on the way out.
| Situation | Main exception | Suppressed |
|---|---|---|
| Body fails, closing succeeds | Body's exception | None |
| Body succeeds, closing fails | Closing's exception | None |
| Body fails, closing fails | Body's exception | Closing's exception(s) |
Adding Your Own
Throwable.addSuppressed(other) is public, so you can use the same idea in your own cleanup code, for example when shutting down several services and wanting to report every failure, not just the first:
public class Shutdown {
public static void main(String[] args) {
IllegalStateException failure =
new IllegalStateException("shutdown had problems");
String[] services = {"cache", "mailer", "payments"};
for (String s : services) {
if (!s.equals("cache")) {
failure.addSuppressed(new RuntimeException(s + " didn't stop"));
}
}
System.out.println(failure.getMessage() + ": "
+ failure.getSuppressed().length + " problems");
for (Throwable t : failure.getSuppressed()) {
System.out.println(" " + t.getMessage());
}
}
}shutdown had problems: 2 problems
mailer didn't stop
payments didn't stopCommon Mistakes
Logging only getMessage(). You lose the suppressed exceptions and the cause.
Assuming the closing exception is the main one. When the body failed, the body's exception is the one you catch.
Writing cleanup in finally that can throw. It replaces the real exception; use try-with-resources or addSuppressed.
Interview Corner
What are suppressed exceptions?
They're exceptions that occur while another exception is already being thrown, typically when try-with-resources closes a resource after the body has failed. Rather than replacing the original exception, they're attached to it with addSuppressed and can be retrieved with getSuppressed(). This preserves the root problem while still recording secondary failures. The feature was added in Java 7.
Practice
- Easy: Change
Resource.close()inSuppressed.javaso it doesn't throw. What does the output become? - Harder: Make
use()succeed and onlyclose()fail. Which exception is caught now, and are there any suppressed ones?
Key Takeaways
Was this tutorial helpful?