Custom AutoCloseable Implementation
Fast recall
Quick revision
Core answer
AutoCloseable and putting deterministic release logic in close(). Define ownership clearly, make close safe and preferably idempotent, and preserve meaningful failures.Interview-ready guide
Interview answer
To make a custom class work with try-with-resources, implement AutoCloseable and define its public close() method. The class should represent something with a clear acquired lifetime—such as a lock lease, temporary workspace, native handle, or scoped session—and close should release that owned state.
The AutoCloseable.close() signature allows Exception, so an implementation can narrow or omit that declaration. java.io.Closeable is more specific for I/O resources: it declares IOException and specifies idempotent closing. AutoCloseable itself does not require idempotence, although making repeated close calls harmless is usually easier for callers and failure recovery.
A sound resource tracks whether it is closed, rejects work after closure, and releases exactly what it owns. If cleanup has several stages, it should attempt necessary releases without casually hiding earlier failures. Close should not normally throw InterruptedException, because generic try-with-resources suppression can interfere with interruption handling.
For example, a temporary-workspace class can create a directory, expose its Path while open, and delete it in close(). A try-with-resources block then makes the directory lifetime visible. A closed-state flag makes a second close harmless and prevents later code from treating the deleted workspace as active.
Not every object needs AutoCloseable. Pure in-memory values without an external lifetime gain nothing from it, and implementing it can imply ownership the class does not have. Use the interface when deterministic release is meaningful, document whether the resource can be reused or closed twice, and test success, body failure, close failure, and partial acquisition.
Custom AutoCloseable Implementation
Direct Answer
A custom resource participates in try-with-resources by implementing AutoCloseable and putting deterministic release logic in close(). Define ownership clearly, make close safe and preferably idempotent, and preserve meaningful failures.
Deep Dive & Architecture
The interface's value is not simply that the compiler calls one method. It gives the type a visible lifetime boundary. Before close, operations are allowed; after close, they should either be harmless or fail predictably. A class that cannot state what it owns and what close releases probably has an unclear resource model.
Construction failure needs the same care as closing. If a constructor acquires several external things and then fails, no object exists for try-with-resources to close. A factory can acquire step by step and release earlier pieces on failure, or the constructor can use local try-with-resources scopes before publishing a fully initialized object.
Idempotence is recommended rather than guaranteed by AutoCloseable. A boolean or atomic state transition often makes repeated close safe, but concurrency rules must be explicit if use and close can race. Thread safety does not appear automatically because a class implements the interface.
Exception design affects callers. Narrow checked exceptions where possible, preserve the original cause, and avoid throwing from close for conditions that can be handled earlier. When close genuinely fails, report enough context for recovery while remembering that try-with-resources may attach that failure as suppressed behind a body exception.
Code Example
import java.nio.file.Files;
import java.nio.file.Path;
class CustomAutoCloseable {
static final class Workspace implements AutoCloseable {
private final Path directory;
private boolean closed;
Workspace() throws Exception {
directory = Files.createTempDirectory("workspace-");
}
Path path() {
if (closed) throw new IllegalStateException("workspace is closed");
return directory;
}
public void close() throws Exception {
if (closed) return;
closed = true;
Files.deleteIfExists(directory);
}
}
public static void main(String[] args) throws Exception {
Workspace workspace = new Workspace();
Path path;
try (workspace) {
path = workspace.path();
if (!Files.isDirectory(path)) throw new AssertionError();
}
workspace.close(); // safe by this class's documented contract
if (Files.exists(path)) throw new AssertionError("directory remains");
System.out.println("closed");
}
}
Check your understanding
Answer these without looking back at the article.
- Does AutoCloseable require close to be idempotent?
- How should partial acquisition be cleaned up if construction fails?
- Why is ownership more important than simply implementing the interface?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.