What are suppressed exceptions?
Most commonly - in try-with-resources:
Junior Level
Definition
Suppressed exceptions - a Java 7+ mechanism that allows one exception to carry a list of other exceptions that occurred in parallel.
Where encountered
Most commonly - in try-with-resources:
try (Resource r = new Resource()) {
r.doWork(); // IOException #1 - main
} // close() throws IOException #2 - suppressed
How to get them
try {
// code
} catch (Exception e) {
System.out.println("Main: " + e.getMessage());
Throwable[] suppressed = e.getSuppressed();
for (Throwable t : suppressed) {
System.out.println("Suppressed: " + t.getMessage());
}
}
Why it’s needed
Before Java 7, if exceptions occurred in both try and finally, the second erased the first (shadowing). Developers lost information about the root cause of the error.
Why specifically Java 7: in Java 7, try-with-resources appeared (JLS 14.20.3), which automatically closes resources. On automatic closing via close(), there’s a high probability that close() will throw an exception simultaneously with an exception from the main try. Without a suppressed mechanism, one exception would erase the other, making debugging impossible. Suppressed exceptions are an infrastructure addition to TWR, not a standalone feature.
When suppressed are NOT added
-
Manual try-finally - in normal
try { ... } finally { close(); }suppressed are NOT added automatically. Exception fromfinallyshadows exception fromtry. Suppressed works only in try-with-resources or on manual call ofaddSuppressed(). -
enableSuppression = false- if exception is created withnew Exception(msg, cause, false, true), calls toaddSuppressed()will be ignored. -
close()doesn’t throw - if resource closes correctly, suppressed list will be empty. This is a normal scenario. -
Exception in only one place - if only
tryfailed (but notclose()) or onlyclose()failed (but nottry), there won’t be suppressed - just one normal exception.
When NOT to rely on suppressed
-
Manual try-finally - suppressed DON’T work automatically. If you write
try { ... } finally { resource.close(); }and both can fail - information will be lost. Use try-with-resources. -
External libraries without TWR - if a third-party library doesn’t implement
AutoCloseableor closes resources manually, suppressed won’t be added. -
Async context - in
CompletableFutureor reactive streams, exceptions from different threads are not aggregated via suppressed automatically. -
Batch processing without pattern - if you process a batch of tasks and each can fail, suppressed won’t add themselves - you need to manually use
addSuppressed().
When NOT to use suppressed exceptions
- Long-lived singleton objects - accumulation of suppressed exceptions leads to memory leak
- Serialization over network - entire suppressed list is transmitted over network, increasing payload
- Highload systems - each suppressed exception = object + stack trace in heap
- As replacement for normal error handling - suppressed don’t replace try-catch on each resource
- In business logic - suppressed are intended for infrastructure code (resources, batch processing)
Middle Level
Internal implementation
In Throwable there is a field private Throwable[] suppressedExceptions (array, not list - for performance).
- Initialized lazily - only on first call to
addSuppressed - Can be disabled on creation:
new Exception(msg, cause, false, true)-enableSuppression = false
How compiler generates code for try-with-resources
When you write:
try (Resource r = new Resource()) {
r.doWork();
}
The compiler expands this to (simplified):
Resource r = new Resource();
Throwable primaryException = null;
try {
r.doWork();
} catch (Throwable e) {
primaryException = e;
throw e;
} finally {
if (r != null) {
if (primaryException != null) {
try {
r.close();
} catch (Throwable closeException) {
primaryException.addSuppressed(closeException);
}
} else {
r.close(); // No main exception - close() throws as normal
}
}
}
Key point: compiler saves a reference to the main exception (primaryException) and on close() error adds it as suppressed to the main one, rather than replacing it.
Throwable constructor parameters
public Throwable(String message, Throwable cause,
boolean enableSuppression, boolean writableStackTrace)
enableSuppression- enables/disables suppressed (true by default)writableStackTrace- if false, stack trace is not filled (speeds up exception creation)
Role in try-with-resources
Scenario:
- Reading file in
try->IOException(disk disconnected) - TWR closes file -> second
IOException(disk gone) - In old Java the second would erase the first
- In modern: first comes to you, second -
e.getSuppressed()
Manual usage
Although TWR does this automatically, you can use it manually:
Exception mainException = new BatchException("Batch failed");
for (Task task : tasks) {
try {
task.execute();
} catch (Exception e) {
mainException.addSuppressed(e);
}
}
if (mainException.getSuppressed().length > 0) {
throw mainException;
}
Limitations
- Self-Suppression:
e.addSuppressed(e)->IllegalArgumentException - Null Suppression:
e.addSuppressed(null)->NullPointerException - Order: suppressed are stored in order of addition. In TWR - reverse order of resource declaration (LIFO)
Senior Level
Memory Leak Risk
Endless addition of suppressed exceptions to a long-lived object (singleton) - memory leak. Each exception carries its stack trace.
Serialization Cost
The entire list of suppressed exceptions is serialized with the main one. In distributed systems - huge volumes of data over the network.
Batch Processing pattern
public class BatchException extends RuntimeException {
private final List<Throwable> errors = new ArrayList<>();
public BatchException(String message) {
super(message);
}
public void addError(Throwable t) {
if (errors.isEmpty()) {
// First exception - as suppressed to itself
addSuppressed(t);
} else {
addSuppressed(t);
}
errors.add(t);
}
public int getErrorCount() {
return errors.size();
}
}
Custom Resource Management
If you write your own resource manager (thread pool, transaction manager) not using standard TWR:
public class CustomResourceManager implements AutoCloseable {
private final List<Resource> resources = new ArrayList<>();
private Throwable primaryException;
public void close() {
for (Resource r : resources) {
try {
r.close();
} catch (Throwable t) {
if (primaryException == null) {
primaryException = t;
} else {
primaryException.addSuppressed(t);
}
}
}
if (primaryException != null) {
throw new RuntimeException("Close failed", primaryException);
}
}
}
Diagnostics
- Logging - Logback/Log4j2 automatically print suppressed with
Suppressed:prefix - Unit tests - check
getSuppressed()when testing resource cleanup - JSON Layout - suppressed as a separate array field in ELK
- Metrics - count suppressed via Micrometer
Interview Cheat Sheet
Must know:
- Suppressed exceptions - Java 7+ mechanism, allows one exception to carry a list of parallel errors
- Appeared together with try-with-resources - for cases when both
tryandclose()fail - In normal
try-finallysuppressed are NOT added automatically - exception fromfinallyshadowstry addSuppressed()can be called manually for batch error processing- Self-suppression (
e.addSuppressed(e)) ->IllegalArgumentException - Suppressed array is initialized lazily - only on first
addSuppressed - Can be disabled:
new Exception(msg, cause, false, true)-enableSuppression = false
Frequent follow-up questions:
- How to get suppressed? -
e.getSuppressed()returnsThrowable[] - When do suppressed NOT work? - Manual try-finally,
enableSuppression = false, only one exception - Why needed in batch processing? - Collect all errors of a batch in one
BatchException - Is there memory overhead? - Yes, each suppressed = object + stack trace, dangerous for singletons
Red flags (NOT to say):
- “Suppressed work in normal try-finally” - no, only in try-with-resources or manually
- “Doesn’t matter how many suppressed to accumulate” - memory leak in long-lived objects
- “Suppressed replace normal error handling” - no, it’s an infrastructure mechanism
- “Serializing suppressed is free” - entire stack trace of each suppressed goes over network
Related topics:
- [[22. What happens if an exception also occurs in the finally block]] - shadowing without suppressed
- [[9. What is try-with-resources]] - main usage scenario
- [[18. What is exception wrapping (wrapping)]] - cause vs suppressed
- [[28. What is exception chaining]] - cause (reason) vs suppressed (parallel error)
- [[11. What is the AutoCloseable interface]] - resources with suppressed on close