How does try-with-resources manage multiple streams, and what is a suppressed exception?
answer
- Multiple resources: semicolon-separated; close in REVERSE order
- Reverse order = inner stream survives outer flush
- Body exception is primary; close exception becomes SUPPRESSED
- getSuppressed()/addSuppressed() retrieve/attach them
- Fixes pre-Java-7 finally masking the real exception
basics
~20 sYou can declare several resources in one try-with-resources; Java closes them automatically in reverse order. If the body throws and a close() also throws, Java keeps the body's exception as the main one and attaches the close() exception as a 'suppressed' exception so neither is lost.
solid answer
~50 sTry-with-resources accepts multiple AutoCloseable resources separated by semicolons (or, since Java 9, effectively-final variables declared outside). At the end of the block it calls close() on each, in reverse order of declaration — which is exactly right for wrapped streams, where the inner resource must outlive the outer one's flush. The subtle part is exception handling: if the try body throws AND a close() throws, the body's exception is propagated as primary and the close() exception is added to it as a *suppressed* exception, retrievable via Throwable.getSuppressed(). This prevents the classic pre-Java-7 bug where a close() failure in a finally block masked the real exception from the body. If only close() throws (body succeeded), that exception propagates normally. This is why hand-written try/finally is discouraged: it's easy to accidentally swallow or overwrite the primary exception.
code
java · 9 linestry (FileInputStream raw = new FileInputStream("a"); // closed second
BufferedInputStream buf = new BufferedInputStream(raw)) { // closed first
throw new IllegalStateException("body failure"); // PRIMARY
} catch (Exception e) {
System.out.println(e.getMessage()); // "body failure"
for (Throwable s : e.getSuppressed()) {
System.out.println("suppressed: " + s); // any close() failures land here
}
}go deeper
Knows you can put more than one resource in try-with-resources and they all get closed.
Explains reverse-order closing and that try-with-resources avoids leaks even on exceptions; uses it routinely.
Explains suppressed exceptions (primary vs. suppressed), getSuppressed/addSuppressed, why reverse order suits wrapped streams, and the Java 9 effectively-final form.
Articulates the error-handling design rationale (never lose the root-cause exception), reviews for hand-rolled finally smells, and codifies resource-management conventions across the codebase.
## The problem try-with-resources solves Before Java 7, you released resources in a `finally` block: ```java FileInputStream in = new FileInputStream(f); try { use(in); // suppose this throws ExceptionA } finally { in.close(); // suppose this throws ExceptionB } ``` If both throw, the `finally`'s `ExceptionB` **replaces** `ExceptionA` as the exception that leaves the method — the *real* failure (A) is silently lost. People wrote elaborate nested try/catch to avoid this, and usually got it wrong. ## try-with-resources basics Java 7 added **try-with-resources (TWR)**: declare resources in parentheses; the compiler generates the close logic. ```java try (var a = open1(); var b = open2()) { // use a and b } ``` Any resource type must implement **`AutoCloseable`** (which declares `close()`); `Closeable` (all I/O streams) extends it. Since **Java 9**, you can also list an *already-declared, effectively-final* variable: `try (existing) { ... }`. ## Reverse-order closing Resources close in **reverse** order of declaration: the last one opened is closed first. This matters for **wrapped streams**. Consider `BufferedOutputStream` over `FileOutputStream`. The buffer must be flushed (which the buffer's close does) *before* the underlying file resource is released. Declaring them outer-first and relying on reverse close gives the correct teardown. In practice you usually declare just the outer wrapper and let its close() cascade to the inner one, but when you hold both, reverse order is what you want. ## Suppressed exceptions — the key concept TWR fixes the masking bug with **suppressed exceptions**. The rule: - If the **body throws** and a **close() also throws**: the body's exception is the **primary** (it propagates), and each close() exception is **attached** to it via `Throwable.addSuppressed(...)`. You retrieve them with `Throwable.getSuppressed()`, and stack traces print them under a `Suppressed:` header. - If the **body succeeds** but a **close() throws**: that close exception propagates normally (nothing to suppress). - If **multiple** resources' close() throw, all are attached as suppressed to the primary. This guarantees the most informative exception — usually the one from your actual work — is never lost, while close failures remain visible for diagnosis. ## Why this beats hand-rolled try/finally Replicating suppressed-exception semantics by hand is tedious and error-prone; TWR does it correctly and concisely. Modern code reviews flag manual close-in-finally as a smell. ## Edge cases worth knowing - A resource that's `null` in the TWR header is simply skipped (no NPE on close). - close() is still called even if the **constructor** of a *later* resource throws, for resources already opened earlier in the same header. - Adding a suppressed exception to itself or to `null` is guarded by the API. ## Quick API recap - `Throwable.addSuppressed(Throwable)` — attach a secondary exception. - `Throwable.getSuppressed()` — return the array of attached ones. - These exist precisely to support TWR.
- If the try body succeeds but close() throws, is that exception suppressed?No. With no primary exception from the body, the close() exception propagates normally as the thrown exception. Suppression only happens when there is already a primary exception to attach to.
- Why is reverse-order closing the correct default for wrapped streams?The outer wrapper (e.g. BufferedOutputStream) must flush its buffer through the inner stream before the inner stream's resource is released. Closing outer-first (reverse of open order) ensures the flush reaches the still-open inner stream.
Closing in reverse order is like undressing: you put on a shirt then a jacket (open inner then outer), and you take off the jacket first (close outer first). Suppressed exceptions are like a doctor noting a minor secondary issue in the chart while still treating the primary diagnosis — both are recorded, but the main one drives action.
saying these in an interview costs you the question
- Claiming resources close in declaration order (it's reverse)
- Thinking the close() exception always wins/replaces the body exception (that was the old bug TWR fixes)
- Not knowing getSuppressed() exists, so 'losing' the secondary exception
- Writing manual try/finally that masks the primary exception