Pages

Wednesday, December 1, 2021

Java 8 IllegalStateException “Stream has already been operated upon or closed” Exception (Stream reuse)

1. Overview

In this short tutorial, We'll see the most common exception "java.lang.IllegalStateException: stream has already been operated upon or closed". We may encounter this exception when working with the Stream interface in Java 8.

Can we collect stream twice after closing?
Java Stream reuse – traverse stream multiple times?




stream has already been operated upon or closed

Exception

java.lang.IllegalStateException: stream has already been operated upon or closed


2. Look At Problem

2.1 From Java 8 API:

A stream should be operated on (invoking an intermediate or terminal stream operation) only once.



2.2 Explanation:

Taking a deeper look at what happens after stream creation.


A) Stream has intermediate operations, short-circuiting terminal operation and terminal operations.
B) Once a Stream is created, A pipe lines associated to this stream will be created.
C) On the stream, We can perform operations like filter(), map(), findAny(), findFirst(), count(), sum() methods on it.
D) Once we perform any operation on the stream, then that operation is placed into the current stream.
E) All operations will be executed and closes stream.

filter(), map() methods are intermediate operations.
findAny(), findFirst() are short-circuiting terminal operation
count() and sum() are terminal operations.

2.3 Example 1:

Next, think if we create a substreams from main stream as below.

String conent = "line 1 \n line 2 \\n line 3 \\n line 4 \\n line 5";

Here a stream is created with pipeline.

Stream mainStream = Stream.of(conent.split("\n"));


Now, calling filter method on mainStream. Now filter operation is placed in the current pipeline.

mainStream.filter(line -> line.indexOf("line") > -1);

Now again calling filter method on mainStream. That will try to put the filter operation in the pipe line. But the pipe line is currently in operated mode with above filter. So, it it not possible to create another pipe line on the same stream.

mainStream.filter(line -> line.indexOf("1") > -1);

So it will throw now Runtime exception.

Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
 at java.base/java.util.stream.AbstractPipeline.(AbstractPipeline.java:203)
 at java.base/java.util.stream.ReferencePipeline.(ReferencePipeline.java:94)
 at java.base/java.util.stream.ReferencePipeline$StatelessOp.(ReferencePipeline.java:696)
 at java.base/java.util.stream.ReferencePipeline$2.(ReferencePipeline.java:165)
 at java.base/java.util.stream.ReferencePipeline.filter(ReferencePipeline.java:164)
 at com.java.w3schools.blog.exceptions.streams.StreamIntermediateException.main(StreamIntermediateException.java:17)

2.4 Example 2:

Another example program to produce the same exception.



// Creating Stream from String.
String conent = "line 1 \n line 2 \\n line 3 \\n line 4 \\n line 5";
Stream mainStream = Stream.of(conent.split("\n"));

// Operation on mainStream 
Optional firstValue = mainStream.findFirst();
System.out.println("firstValue : " + firstValue.get());

// Operation on mainStream

Optional anyValue = mainStream.findAny();
System.out.println("anyValue : " + anyValue.get());

See in this example, calling two methods on mainStream.

First when calling findFirst() then it returns value. But next invoking findAny(), at this time stream or pipe line is already closed. So it can not perform operation on the same stream.


Output:

firstValue : line 1 
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
 at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
 at java.base/java.util.stream.ReferencePipeline.findAny(ReferencePipeline.java:548)
 at com.java.w3schools.blog.exceptions.streams.StreamCloseException.main(StreamCloseException.java:25)

Here firstValue is printed but while getting value from findAny() operation it has failed to get the stream because stream is already closed.

Note:

A Stream should be operated on (invoking an intermediate or terminal stream operation) only once. A Stream implementation may throw IllegalStateException if it detects that the Stream is being reused.

3. Solutions

We have many scenarios where the same stream is required to perform many operations. See the above example 2 where two values are needed.

To solve this issue, We need to create two-stream instances for these two separate operations.


Note:

Instead of creating two stream objects separately, We can use built in API Functional Interface Supplier<T> which is super handy in usage.

Supplier> supplier = () -> Stream.of(conent.split("\n"));

// findFirst Operation
Optional firstValue = supplier.get().findFirst();
System.out.println("firstValue : " + firstValue.get());

// findAny Operation
Optional anyValue =supplier.get().findAny();
System.out.println("anyValue : " + anyValue.get());



In this program, created a Supplier object with type of Stream<String>. Supplier has a abstract method that is get() which returns Stream<String>. Now, We have to use Lambda Expression for Supplier Functional Interface with () zero arguments.

So, Whenever we call supplier.get() method than a new fresh Stream<String> object will be created and can perform any stream operation on it.

Output:

Output is returned properly without any errors.

firstValue : line 1 
anyValue : line 5

4. Conclusion

In this article, We've seen how to fix "java.lang.IllegalStateException: stream has already been operated upon or closed" problem when working with Java 8 Stream objects.

And also seen how to use terminal operations on the same stream using Supplier interface which avoids famous exception "stream has already been operated upon or closed".

All the examples shown in this article are available over GitHub with complete examples.

No comments:

Post a Comment

Please do not add any spam links in the comments section.