Import Service: Suppressed Exceptions, Catch Order and var Boundaries
A nightly import service opens two resources in one try-with-resources header: try (BufferedReader in = new BufferedReader(new FileReader(path)); AuditLog log = new AuditLog(logDir)) { process(in); }. AuditLog implements AutoCloseable; its close() flushes to disk and can throw IOException. When logDir is read-only, the AuditLog constructor throws IOException, and the stack trace shows a second, suppressed exception attributed to in.close(). Elsewhere a catch chain reads catch (Exception e) { ... } catch (IOException e) { ... } and javac refuses to compile it; a multi-catch catch (IOException | FileNotFoundException e) is rejected as well. A helper declares var reader = new BufferedReader(new FileReader(path)); and a colleague tries var in a catch clause and var for only one of two lambda parameters. A finally block ends with return status; and an IOException thrown inside the try never reaches the caller. The team also wants to know what happens when the try body throws and close() throws at the same time.
1. Describe the closing order and the suppressed-exception behaviour for both failure modes: an exception in the resource initializer, and an exception in the try body.
Read the worked answer
Resources are closed in the reverse of their declaration order: log.close() then in.close().
Initializer throws. new AuditLog(logDir) fails, so log is never registered. The already-initialized in is still closed, and if in.close() throws, its exception is suppressed onto the constructor's IOException. The constructor's exception is primary and propagates. (The same rule applies one level down: if new BufferedReader(...) failed after new FileReader(...) succeeded, the FileReader would be closed.)
Body throws plus close throws. The exception from process(in) is primary; the exception from log.close() is suppressed onto it, and then in.close() runs, whose exception is also suppressed. Nothing is lost, but suppressed exceptions are invisible unless you look: e.getSuppressed() returns them, and default printStackTrace prints only a "... N more" style summary. Logging frameworks usually record only the primary throwable, so a flush failure that actually corrupted the audit trail can vanish from dashboards.
One consequence worth flagging: if close() throws while the body completed normally, that close exception propagates as the primary exception, so the method can fail even though the import succeeded.
2. Why are both catch clauses rejected, and why is the multi-catch rejected? Rewrite the chain correctly.
Read the worked answer
Ordering. Exception handlers are tested top to bottom, and the compiler performs an exact reachability check on catch clauses. catch (Exception e) already handles every IOException, so the later catch (IOException e) block can never execute; javac reports "exception IOException has already been caught". The rule is specific before general:
try {
process(in);
} catch (FileNotFoundException e) { // most specific
...
} catch (IOException e) { // then the supertype
...
} catch (Exception e) { // optional catch-all last
...
}
Multi-catch. catch (IOException | FileNotFoundException e) is rejected because the alternatives are related by subclassing: FileNotFoundException is an IOException, so the union is redundant. Multi-catch also requires pairwise-disjoint types. Two further rules that trip people: the multi-catch parameter is implicitly final, so assigning to e inside the block does not compile; and its static type is the least upper bound of the alternatives, so only members common to all of them can be called - you cannot call FileNotFoundException.getFile() from a block that also catches IOException.
3. State the rules that decide where var is legal, apply them to the three attempts in the setup, and explain why the IOException vanishes in the finally block.
Read the worked answer
var is a local-variable type inference feature, not a type. It is legal for local variable declarations, for-loop indices, try-with-resources variables and (since Java 11) lambda parameters, and it requires an initializer on the same statement so the type can be inferred. It is illegal for fields, method and constructor parameters, and catch parameters, and it cannot be used with a bare array initializer (var a = {1,2}; fails; var a = new int[]{1,2}; works) or with null.
Applied here:
var reader = new BufferedReader(new FileReader(path));is legal;readeris inferred asBufferedReader.catch (var e)is illegal - catch parameters are explicitly excluded, so you must write the exception type.(var a, Integer b) -> ...is illegal: once a lambda parameter usesvar, every parameter must use it, and annotations go on thevar.
The disappearing IOException is caused by the return in the finally block. A finally block that completes abruptly (via return, break, continue or a thrown exception) discards any pending exception or return value from the try. The IOException is simply dropped, with no trace. Remove the return from finally, keep it for cleanup only, or replace the whole structure with try-with-resources.