Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java Stream.close: terminal traversal does not close every source

Last updated: 29 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

A Stream can have a close handler, but a terminal operation does not by itself invoke that handler.

Download Java source kit

The complete program targets Java 8. Compile it as one source file; its output is checked against the lesson.

Traversal and resource release are different events

A file-backed stream can hold a reader while elements are processed. Reaching count() or collecting the values is not a general resource-release protocol. Close the stream with try-with-resources when its source owns an external resource.

This fixture uses Stream.of and an onClose counter rather than opening a file, making the lifecycle distinction visible without depending on filesystem state. The count completes while the close counter remains zero; leaving the try block changes it to one.

Do not return ownership ambiguously

A helper that returns a file-backed stream transfers a resource obligation to its caller. Document that obligation or collect the required bounded data inside the helper and close it there. Collecting an unbounded input is not a safe way to avoid documenting ownership.

A stream is single-use. Closing one also prevents later traversal, and exceptions in processing still require release. If traversal and closing both fail, try-with-resources records the closing failure as suppressed rather than replacing the original failure.

Working program

Java
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
public class ReceiptStreamLifetime {
    public static void main(String[] args) {
        AtomicInteger closed = new AtomicInteger();
        try (Stream<String> receiptIds = Stream.of("R-21", "R-22").onClose(closed::incrementAndGet)) {
            System.out.println(receiptIds.count());
            System.out.println("before close=" + closed.get());
        }
        System.out.println("after close=" + closed.get());
    }
}

Output

Output
2
before close=0
after close=1

Costs and boundaries

The example uses constant application state and a two-element source. A real lazy source can bound memory while still keeping descriptors open; memory usage and descriptor lifetime require separate checks.

Common Mistakes

  • Do not assume collect() closes a Files.lines stream.
  • Do not return a resource-backed stream without explaining who closes it.
  • Do not reuse a consumed stream.

Read next

Java file I/O: UTF-8, streaming reads, and path ownership, Java try-with-resources: close order and suppressed failures, Java streams: lazy pipelines and bounded results.

java
stream-resource-lifetime
Storage details