📁 I/O, Files & Networking · Intermediate

Closing resources in Java

try-with-resources for files and streams; Files.lines must be closed.

🧩 The mysteryYour server works for hours, then every request fails with "Too many open files". Nothing crashed earlier. Something has been quietly borrowing and never giving back…

Borrowed handles

Files, sockets and streams hold operating-system handles, and the OS gives each process only a limited number. try-with-resources closes anything that implements **AutoCloseable on every** way out of the block: normal end, return or exception.

try (var in = Files.newBufferedReader(p)) {
    return in.readLine();
} // in.close() runs here, always
🔮 Predict it

Close before catch?

The resource prints when it closes. What prints?

try (AutoCloseable a = () ->
        System.out.println("closed")) {
    throw new IllegalStateException();
} catch (Exception e) {
    System.out.println("caught");
}
  1. caught closed
  2. closed caught
  3. caught
Show the answer

closed then caught — the resource is closed as soon as the try block exits, even by an exception, before the catch block runs.

Reverse order

With several resources, they close in reverse order of declaration: the later one may depend on the earlier one, so it goes first.

try (var in = Files.newBufferedReader(src);
     var out = Files.newBufferedWriter(dst)) {
    in.transferTo(out);
} // out closed, then in
⚠️ The trap

Files.lines is a hidden open file

Files.lines (and Files.walk) keep the file open until the stream is closed — and a terminal op like count() does not close it. Each call leaks a handle. Put the stream in try-with-resources.

// leaks a file handle:
long n = Files.lines(Path.of("app.log"))
    .filter(l -> l.contains("ERROR"))
    .count();

Must be AutoCloseable

Having a close() method isn't enough: a try-with-resources resource must **implement AutoCloseable** (or its subtype Closeable), otherwise it's a compile error.

class Conn {             // no interface
    void close() { }
}
try (var c = new Conn()) { } // won't compile
🤔 Think first

Two exceptions at once

The body throws exception X, then close() throws exception Y. What does your catch block receive?

Think about it, then reveal the answer

X, with Y attached as a suppressed exception (read it with X.getSuppressed()). The body's error is the important one. Old finally-based code lost X and reported Y instead.

💼 In the real world

Leaks in production

"Too many open files" outages usually trace back to one unclosed Files.lines, socket or JDBC connection in a hot path. Since Java 7, try-with-resources is the standard answer, and static analysers flag resources created outside it.

Key takeaways

  1. try (var in = …) { } closes in automatically
  2. Several resources close in reverse order of declaration
  3. Files.lines/Files.walk hold an open file — close them!
  4. close() failures become suppressed exceptions
🤯 Did you know?

try-with-resources arrived in Java 7, and Java 9 made it shorter: you can write try (existingVar) { … } with an effectively final variable declared earlier.

Practice questions

What does this print?

record R(String n) implements AutoCloseable {
    public void close() {
        System.out.println("close " + n);
    }
}
void main() {
    try (var a = new R("a"); var b = new R("b")) {
        System.out.println("body");
    }
}
  1. body close a close b
  2. body close b close a
  3. close a close b body
  4. body
Check your answer

body close b close a. The body runs first, then resources are closed in reverse order: b before a.

What does this print?

class Conn {
    void close() { System.out.println("x"); }
}
void main() {
    try (var c = new Conn()) {
        System.out.println("hi");
    }
}
  1. hi x
  2. hi
  3. Compile error
  4. Throws IllegalStateException
Check your answer

Compile error. Having a close() method isn't enough: a try-with-resources resource must implement AutoCloseable (or Closeable).

Next: what exactly goes wrong when a file is missing — and why lambdas and IOException don't get along.