🔴 The Error You're Seeing

Confirm this matches your console output. If it does, you're in the right place.

ERROR LOGjava.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)

Solution 1✓ Most common cause

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);
Solution 2

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);
Solution 3

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();
Solution 4

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()); }
Solution 5

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

Java 8

Single-use rule enforced identically.

Java 16+

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.