How do I see the close failures that were suppressed onto the exception I caught?
← Error Handling and Exceptions · Ref: Q1920
Call 'suppressed()' on the Exception you caught. It returns a 'List of Exception': the close failures that came AFTER this one and were attached to it rather than replacing it, in the order the closes ran.
catch -> ex as Exception stdout.println(`caught: ${ex.reason()}`) for failure in ex.suppressed() stdout.println(` suppressed: ${failure.reason()}`)
Wherever EK9 generates closing - the end of a try, the end of a statement that made an unnamed closeable, and the chain after a type's 'operator close' - every close runs, the first Exception is the one that propagates and every later close failure is suppressed onto it. When the try body threw, the body's exception is the one you catch and the close failures are in its suppressed() list.
A suppressed failure is an Exception like any other, so it can carry suppressed failures of its own. A type's generated close chain fails as ONE close: its first failure, with the rest of that chain suppressed onto it. So when a try body throws and then the resource's chain fails, you catch the body's exception, its suppressed() holds the chain's first failure, and THAT failure's suppressed() holds the rest - walk it recursively, as 'describe' does below, to see everything.
The List is always set (a collection is set when it is created) and is empty when nothing was suppressed, so you never need a guard before iterating it, and 'length ex.suppressed()' is 0 for an ordinary exception. Every sub-type of Exception inherits suppressed(). A Panic is never caught by the close regions, so it can never be a suppressed failure.
In the program below the Ledger closes its three parts last first and two of those closes throw: the first failure ('index') is caught with 'journal' suppressed onto it. When the posting itself fails, the posting's exception is caught, the ledger's 'index' failure is suppressed onto it, and 'journal' is suppressed onto 'index'. An exception with nothing suppressed answers an empty List.
Example
defines module qa.errorhandling.inspectsuppressed defines class Part name as String? default private Part() Part() -> label as String name: label operator close as pure stdout <- Stdout() stdout.println(`closing ${name}`) if name <> "totals" throw Exception(`close of ${name} failed`) default operator ? Ledger journal as Part: Part("journal") index as Part: Part("index") totals as Part: Part("totals") default operator close default operator ? defines function report() -> ex as Exception Stdout().println(`caught: ${ex.reason()}`) for failure in ex.suppressed() describe(failure, " ") describe() -> failure as Exception indent as String Stdout().println(`${indent}suppressed: ${failure.reason()}`) for inner in failure.suppressed() describe(inner, indent + " ") defines program InspectSuppressed() stdout <- Stdout() try try -> ledger <- Ledger() stdout.println("using the ledger") catch -> ex as Exception report(ex) try try -> ledger <- Ledger() require ledger? throw Exception("the posting failed") catch -> ex as Exception report(ex) try throw Exception("nothing to close") catch -> ex as Exception report(ex) stdout.println(`nothing suppressed: ${length ex.suppressed() == 0}`)
Common mistakes
E50060 — EK9's Exception has no Java-style 'getSuppressed()'; the method is 'suppressed()' and it returns a List of Exception. See ek9 -h Exception for the full API.
Incorrect:
for failure in ex.getSuppressed()
Correct:
for failure in ex.suppressed()
Other ways to ask this
- How do I get the suppressed exceptions in EK9?
- What is the EK9 equivalent of Java's getSuppressed()?
- Several closes failed - where did the other exceptions go?
- How do I log every close failure, not just the first one?
- Why does a suppressed exception have suppressed exceptions of its own?
Coming from another language?
Java: Throwable.getSuppressed() returns an array of what try-with-resources attached with addSuppressed. Python: the 'during handling of the above exception' chain (__context__). C#: no equivalent - a throwing Dispose replaces the original exception. EK9: ex.suppressed() returns a List of Exception, filled by every generated close (try resources, statement-owned temporaries and the property chain), never null and empty when nothing was suppressed.
Keywords: exception, try with resources, suppressed, suppressed(), List of Exception, close, getSuppressed, operator close