Do terminal operations close the stream?

dirPath contains 200k files. I want to read them one by one and do some processing. The following snippet causes java.nio.file.FileSystemException: dirPath/file-N Too many open files. Isn't the terminal operation forEach() supposed to close the open stream (i.e. the open file) before moving to the next one? In other words, do I have to add try-with-resources for the streamed files?

Files.list(dirPath)
     .forEach(filePath -> {
              Files.lines(filePath).forEach() { ... }
              });

Solution 1:

No forEach does not close the stream (created by Files.list or Files.lines). It is documented in the javadoc, for example for Files.list:

The returned stream encapsulates a Reader. If timely disposal of file system resources is required, the try-with-resources construct should be used to ensure that the stream's close method is invoked after the stream operations are completed.

Solution 2:

A nested forEach is the wrong tool, in most cases.

The code

Files.list(dirPath).forEach(filePath -> Files.lines(filePath).forEach(line -> { ... });

can and should be replaced by

Files.list(dirPath).flatMap(filePath -> Files.lines(filePath)).forEach(line -> { ... });

or well, since it’s not that easy in this case:

Files.list(dirPath).flatMap(filePath -> {
    try { return Files.lines(filePath);}
    catch(IOException ex) { throw new UncheckedIOException(ex); }
}).forEach(line -> {  });

as a side-effect, you get the following for free:

Stream.flatMap(…):

Each mapped stream is closed after its contents have been placed into this stream.

So that’s the preferred solution. Or well, to make it entirely correct:

try(Stream<Path> dirStream = Files.list(dirPath)) {
    dirStream.flatMap(filePath -> {
        try { return Files.lines(filePath);}
        catch(IOException ex) { throw new UncheckedIOException(ex); }
    }).forEach(line -> { });
}