Skip to content

How to Properly Close Java Streams

DodaTech Updated 2026-06-24 3 min read

In this tutorial, you'll learn about How to Properly Close Java Streams. We cover key concepts, practical examples, and best practices.

The Problem

Your Java application leaks file handles or sockets. Files.lines(), BufferedReader.lines(), or custom Stream implementations are not closed after use. The OS eventually runs out of file descriptors, causing Too many open files errors.

Quick Fix

Fix 1: Files.lines() Must Be Closed

WRONG — using Files.lines() without closing:

Stream<String> lines = Files.lines(Paths.get("data.csv"));
lines.forEach(System.out::println);
// (file handle is never closed — resource leak)

RIGHT — use try-with-resources:

try (Stream<String> lines = Files.lines(Paths.get("data.csv"))) {
    lines.forEach(System.out::println);
} catch (IOException e) {
    e.printStackTrace();
}
// (stream and underlying file handle are closed automatically)

Fix 2: BufferedReader.lines() Requires Closing

BufferedReader reader = new BufferedReader(new FileReader("data.txt"));
Stream<String> lines = reader.lines();
// (both reader and stream must be closed)

WRONG — closing only the stream:

lines.close();
// (reader is not closed)

RIGHT — use try-with-resources on the reader:

try (BufferedReader reader = Files.newBufferedReader(Paths.get("data.txt"))) {
    reader.lines().forEach(System.out::println);
} catch (IOException e) {
    e.printStackTrace();
}
// (reader and stream are both closed)

Fix 3: Infinite Streams (Stream.generate/iterate)

Stream.generate(Math::random)
    .forEach(System.out::println);
// (infinite loop — never terminates)

RIGHT — limit the stream:

Stream.generate(Math::random)
    .limit(10)
    .forEach(System.out::println);
// (terminates after 10 elements)

Fix 4: Custom Stream Implementation

WRONG — implementing Stream without closing underlying resources:

class MyStream<T> implements Stream<T> {
    private Connection connection;

    // onClose() handler not set — resources not released
}

RIGHT — use onClose to register cleanup:

Stream<T> stream = StreamSupport.stream(spliterator, false)
    .onClose(() -> {
        connection.close();
        file.close();
    });

Fix 5: Chained Stream Operations

// Intermediate operations (filter, map) do not open resources.
// Terminal operations (collect, forEach) close the stream.
// But if a terminal operation throws, the stream is not closed:
try {
    Files.lines(Paths.get("data.csv"))
         .map(line -> { throw new RuntimeException(); })
         .collect(Collectors.toList());
} catch (RuntimeException e) {
    // The stream is not closed due to the exception!
}

RIGHT — wrap in try-with-resources:

try (Stream<String> lines = Files.lines(Paths.get("data.csv"))) {
    List<String> result = lines
        .map(line -> { throw new RuntimeException(); })
        .collect(Collectors.toList());
} catch (RuntimeException e) {
    // stream is closed by try-with-resources
}

Fix 6: Check for Too Many Open Files

# Linux:
lsof -p $(pgrep -f myapp) | wc -l
# (monitor the count over time)

ulimit -n
# 1024 — default file descriptor limit

Increase the limit in the JVM:

java -Xms256m -Xmx1g -jar myapp.jar
# And in the OS:
ulimit -n 65536

Use DodaTech's Resource Monitor to track file descriptor usage, detect stream leaks, and alert on resource exhaustion in Java applications.

Prevention

  • Always use try-with-resources for Files.lines() and BufferedReader.lines().
  • Close the underlying reader, not just the stream.
  • Register onClose handlers for custom streams.
  • Limit infinite streams with limit().
  • Monitor file descriptor usage in production.

Common Mistakes with stream close

  1. Non-exhaustive pattern matches that compile with warnings then crash at runtime
  2. Misunderstanding that String is [Char] with poor performance for large text operations
  3. Using foldl instead of foldl' causing stack overflow on large lists

These mistakes appear frequently in real-world JAVA code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

### What happens if I do not close a Stream?

The underlying resource (file handle, socket, database cursor) is never released. Eventually, the OS runs out of file descriptors. The garbage collector does not close resources — only the close() method or try-with-resources releases them.

Does collect() or forEach() automatically close the stream?

Yes, terminal operations close the stream after completion in most JDK implementations. However, if an exception is thrown during the terminal operation, the stream may not be closed. Always use try-with-resources for guaranteed cleanup.

How do I check for unclosed streams in my code?

Use static analysis tools like SpotBugs, IntelliJ inspections, or SonarQube. They flag Files.lines() or stream operations without try-with-resources. Enable the "resource should be managed by try-with-resources" rule.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro