As I constantly reiterate, it depends on your use case. The corollary I use is: the context of the use case determines the code.
So, Files (as already stated), Scanners, and Sockets need to be manually closed. The rule is to close all IO-based streams.
What about Collections, Arrays, and Generators? Quoting from the Baeldung article: Should We Close a Java Stream?
Most of the time, we create Stream instances from Java collections,
arrays, or generator functions. For instance, here, we're operating on
a collection of String via the Stream API:
List colors = List.of("Red", "Blue", "Green") .stream()
.filter(c -> c.length() > 4) .map(String::toUpperCase)
.collect(Collectors.toList());
Sometimes, we're generating a finite or infinite sequential stream:
Random random = new Random(); random.ints().takeWhile(i -> i <
1000).forEach(System.out::println);
Additionally, we can also use array-based streams:
String[] colors = {"Red", "Blue", "Green"};
Arrays.stream(colors).map(String::toUpperCase).toArray()
When dealing with these sorts of streams, we shouldn't close them
explicitly. The only valuable resource associated with these streams
is memory, and Garbage Collection (GC) takes care of that
automatically.
Another use case that I wasn't aware of is:
- Streams passed to flatMap will be closed regardless of closing the Stream that contains them. From the javadocs
Returns a stream consisting of the results of replacing each element
of this stream with the contents of a mapped stream produced by
applying the provided mapping function to each element. Each mapped
stream is closed after its contents have been placed into this stream.
(If a mapped stream is null an empty stream is used, instead.)
Kudos to Mike for his post on Closing Java Streams with Autoclosable where he points this out.
So to summarize, the use case determines how and when to close a stream.