How to resolve error “Stream has already been operated upon or closed” / Why “Stream has already been operated upon or closed” occurs

A Bit about Stream

Stream is a new feature added in java 8 and represents a sequence of elements. A stream originates from a collection which holds these elements. This collection may be an array, a list, a set, a map etc. It should be remembered that a stream itself does not hold any elements.

A stream supports many operations. It can be iterated, sorted, filtered, reduced etc.

Error – Cause

As stated above, many operations can be applied over a stream. Every operation consumes the stream and a stream can be consumed only once. It cannot be reused. If the same stream is used more than once, an error of the form java.lang.IllegalStateException: stream has already been operated upon or closed is raised. For Example, the following code will generate this error :

//create an array
String[] strArr = { "a", "b" };
//initialize a stream to this array
Stream stream = Arrays.stream(strArr);
//convert this stream to a list :: Stream is consumed here
List list = stream.collect(Collectors.toList());
System.out.println(list);
//iterate over this list :: Raises an error
stream.forEach(str -> System.out.println(str));

Output

[a, b] Exception in thread “main” java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:274)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
    at Codippa.main(Codippa.java:53)

This is because the stream obtained from the array has already been consumed at Line 6 where we are using it to convert to a List and we are re-using it on Line 8 for iterating over the underlying array, which is not permitted and hence an exception is thrown.

Solution

It is not permitted to re-use a stream after it has been consumed once. From javadocs of java.util.Stream interface,

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.

But there might be some scenarios where you have to re-use a stream such as once for filtering a collection to display elements matching some condition and then for iterating over entire stream to display all elements. In such cases following code should help you out :

// create an array
String[] strArr = { "a", "b" };
// initialize a stream to this array
Stream stream = Arrays.stream(strArr);
// convert this stream to a list :: Stream is consumed here
List list = stream.collect(Collectors.toList());
System.out.println(list);
// return a stream from supplier and iterate over it
Supplier> supplier = () -> Stream.of(strArr);
supplier.get().forEach(str -> System.out.println(str));

Output

[a, b] a
b

No error and a correct output, which means we are able to get a solution. Last two lines of the above code require an explanation.

java.util.function.Supplier is a Functional interface with only one method get() which accepts no parameters and returns a value. The expression () -> Stream.of(strArr) is a Lambda expression which is calling of() method of java.util.stream.Stream interface. of() method of java.util.stream.Stream interface returns a value (a Stream object).

Hence the expression () -> Stream.of(strArr) accepts no arguments and returns a value. Thus it can easily represent a Supplier (whose get() method accepts no arguments and returns a value).

Since of() method called in Lambda expression returns a stream of type String, it is assigned to a Supplier> and hence, get() method of Supplier returns a Stream. Now using forEach method of stream, we can iterate over it.

 Let’s tweak in

      1. A Functional interface is always annotated with @FunctionalInterface.
      2. A stream implementation class has a field linkedOrConsumed. This is a boolean field which is set to true once the stream has been utilized. Before opening the stream, this field is checked to be false. If it is found true, a java.lang.IllegalStateException is thrown.
      3. of() method of java.util.stream.Stream interface is a static method.
      4. of() method returns a new stream pointing to the elements of the collection passed to it. Hence the above piece of may also be written as :
        // create an array
        String[] strArr = { "a", "b" };
        // initialize a stream to this array
        Stream stream = Arrays.stream(strArr);
        // convert this stream to a list :: Stream is consumed here
        List list = stream.collect(Collectors.toList());
        System.out.println(list);
        // get a stream from array
        Stream stream2 = Stream.of(strArr);
        // iterate over the stream
        stream2.forEach(str -> System.out.println(str));

        Thus both the above codes work because we are working on two different streams altogether.

Hope this post helped you out. If there is any explanation required, do not hesitate to clarify !!!

2 Comments

Leave a Reply