🔴 The Error You're Seeing
Confirm this matches your console output. If it does, you're in the right place.
java.lang.IllegalStateException: stream has already been operated upon or closed
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:260)
at java.base/java.util.stream.ReferencePipeline.count(ReferencePipeline.java:750)⚡ Quick Fix Works 80% of the time
Create the stream fresh at each point of use.
long n = orders.stream().filter(...).count(); // pass 1
List<Order> big = orders.stream().filter(...).toList(); // pass 2: NEW stream🧠 Why this Happens
Tap to expand the deep technical explanation
Every Stream pipeline is a linked chain of stages carrying a linkedConsumed flag. AbstractPipeline.evaluate flips that flag during the FIRST terminal invocation; any subsequent evaluate sees it and throws. Collections are reusable containers while streams are one-shot traversals over them - confusing the two is the root cause. Files.lines adds a twist: closing the resource also poisons its stream with this same message.
The HITEC City Parking Spot Analogy:
A cinema ticket: valid for one seat at one screening. Rewatching tomorrow means buying a new ticket - i.e., calling stream() again.
🔁 How to Reproduce Confirm this is your error
Stream<Integer> s = Stream.of(1, 2).filter(x -> x > 0); s.count(); // fine s.count(); // IllegalStateException: stream has already been operated upon or closed
🛠️ Solutions (5 Ways to Fix)
Create fresh streams per consumption
👉 Use this if the source collection is stable and cheap to traverse repeatedly.
Call collection.stream() at each consumption site so every pipeline starts life unconsumed.
long active = users.stream().filter(User::active).count();
User first = users.stream().filter(User::active).findFirst().orElse(null);Materialize once into a List, reuse the collection
👉 Use this if one filtered result feeds many consumers.
Terminal-collect once with toList(), then operate on the resulting List as often as needed; no stream object survives to be reused by mistake.
List<Order> pending = orders.stream()
.filter(Order::pending).toList();
report(pending);
audit(pending);Supplier<Stream<T>> factory for repeated pipelines
👉 Use this if the same multi-step pipeline runs periodically.
Wrapping construction in a supplier makes new-stream-per-use structural: invoking get() always yields virgin pipelines.
Supplier<Stream<Event>> recent =
() -> events.stream().filter(Event::isRecent);
long n = recent.get().count();Extract a pipeline-builder method
👉 Use this if multiple call sites need the same filtering chain.
A method that RETURNS a fresh stream from the source gives every caller their own traversal without sharing state.
Stream<Order> pendingOrders(List<Order> all) {
return all.stream().filter(o -> !o.paid());
}Keep terminal operations inside try-with-resources
👉 Use this if streaming files or other closeable sources.
Files.lines must be consumed before its try block exits - consuming later throws this same IllegalStateException because the resource is closed.
try (Stream<String> lines = Files.lines(path)) {
long bad = lines.filter(l -> l.contains("ERROR")).count();
} // stream auto-closed here, never used after📋 Version Notes
Single-use rule enforced identically.
Stream.toList() simplifies materialize-once patterns; parallel streams share the same single-use rule.
🛡️ How to Prevent This Next Time
Treat Stream like an Iterator, not like a Collection: one terminal operation per object. When in doubt, re-derive from the backing collection.