About DavidHLPL · RHINE LAB RSS ↗
FILE X-1038 / Backend engineering

AutoCloseable: Resource Ownership and Close-Exception Semantics

ENGINEERING NOTES

Published / DavidHLPL

AutoCloseable is a resource-release protocol: code that owns a resource gives its lifetime to try-with-resources, which calls close() when the scope ends. This page focuses on ownership, ordering, and exception propagation rather than a catalog of stream, connection, or JDBC tutorials.

Core mechanism

1. Ownership comes before syntax

Implementing AutoCloseable means an object supports closing; it does not decide who owns it. The code that creates and is responsible for releasing a resource should put it in TWR. A borrower must not close a shared resource while another owner still uses it.

try (Connection c = dataSource.getConnection();
     PreparedStatement p = c.prepareStatement(sql)) {
    // c and p are used only inside this scope
}

Once initialization succeeds, the compiler-generated logic closes resources on both normal and exceptional exit. Multiple resources close in reverse declaration order: p before c, because a wrapper or dependent resource should be released before its underlying resource.

2. Primary and suppressed exceptions

  • An exception from the try body is the primary exception.
  • A close() failure is attached through primary.getSuppressed() instead of replacing the primary exception.
  • If the body succeeds but close() fails, the close failure is thrown.
  • If initialization fails, later resources are not treated as acquired; already-acquired resources are still cleaned up according to the protocol.

This is safer than handwritten try-finally: a rethrown exception in finally can hide the business exception, while TWR preserves both pieces of evidence.

3. The smallest close protocol

final class Handle implements AutoCloseable {
    private boolean closed;

    @Override public void close() throws Exception {
        if (!closed) {
            closed = true;
            release();
        }
    }
}

close() should be as idempotent as practical and should release resources rather than start new business work. If closing flushes data or determines whether output is complete, make failure observable. If it is compensating cleanup such as deleting a temporary file, logging and suppressing an accepted failure can be valid—but the policy must be explicit.

Applicability

  • The object holds a file descriptor, connection, cursor, lock, temporary directory, or another external resource that needs explicit release.
  • Ownership can be expressed in one scope, or transferred through an explicit wrapper.
  • Several resources have dependencies and need deterministic reverse-order cleanup.
  • Close failures must remain visible to callers through Exception, IOException, or a domain-specific contract.

Closeable is the I/O-specialized child of AutoCloseable; its close() commonly declares IOException. A general business resource can implement AutoCloseable directly.

Not applicable and risks

  • Do not put a borrowed shared connection, container-managed executor, or global singleton in TWR that does not own it; closing it breaks another caller’s lifetime.
  • The exception type from close() still follows the implementation and JDK API contract; not every resource closes silently.
  • Idempotence is a recommended production property, not a guarantee that every library implementation makes repeated close calls harmless; check the specific API.
  • Reverse ordering solves resource dependency, not transaction commit, message acknowledgement, or business compensation. Those semantics need explicit design.
  • Versions, connection pools, and serialization clients may change implementation details; compiler expansion is not a public API contract.

Minimum verification

  1. Use an observable resource that records open, use, and close; test both normal return and a body exception, asserting one close.
  2. Declare two dependent resources and record events; assert that the later declaration closes first.
  3. Make both use() and close() throw; inspect the primary exception and getSuppressed() contents.
  4. Test a borrowed/shared resource separately: the owner should still be able to use it after the borrower scope ends. If not, remove TWR or define an explicit lifetime wrapper.

Evidence and uncertainty

  • Source facts: legacy-java-auto-closeable describes Java 7 AutoCloseable, TWR reverse order, primary/suppressed exceptions, the Closeable relationship, custom resources, and idempotent close().
  • Synthesis in this page: Ownership is made a precondition for the syntax, and close failures are divided into failures that must propagate and cleanup failures that may be logged by policy.
  • Unconfirmed: Exact idempotence, exception types, and transaction behavior for JDBC, filesystems, and third-party clients require the relevant versioned API and a runtime test; examples are not universal guarantees.

Research notes

  1. Original document: src/content/note/en/java-auto-closeable.md
  2. Source revision: f49953fb0d4bd52a39bf81167b0c4b7dd7d78a64
  3. Evidence status: active

Original source ↗