Close

Java 8 Streams - Side Effects

[Updated: Nov 17, 2016, Created: Nov 17, 2016]

A side effect is an action taken from a stream operation which changes something externally.

The change may be changing some variable values in the program or it can be sending a JMS message, sending email, printing with System.out.println or in a UI application changing the state of a button from enabled to disabled etc.



What operations should apply side-effects?

The pipeline operations ForEach(), ForEachOrdered() and peek() which returns void, are meant to produce side effects.

The intermediate operations with behavioral parameters which usually return a non-void value should entirely be avoided to apply side effects e.g. filter() , map() etc.

Intermediate operation peek() should be limited to side-effects like logging and debugging only.



Side-effects and stateful/stateless operations

Side effects may be applied via a stateful action (not recommended) i.e. side effects updating some mutable shared variable.

Side effects can also be applied via stateless actions e.g. logging, sending messages etc.

Stateful operations should entirely be avoided as we discussed in the last tutorial, even if we use them from operations like forEach(). Side effects which work in a stateless manner should be applied only from forEach(), forEachOrdered() and peek().


Side-effects in behavioral parameters to stream operations are, in general, discouraged, as they can often lead to unwitting violations of the statelessness requirement, as well as other thread-safety hazards.


If the behavioral parameters do have side-effects, unless explicitly stated, there are no guarantees as to the visibility of those side-effects to other threads, nor are there any guarantees that different operations on the "same" element within the same stream pipeline are executed in the same thread



Side-effects and parallelism

In parallel pipeline, the operation peek() and forEach() do not respect encounter order. They are also non-deterministic (producing different results on multiple executions). We should only apply side-effects from these operations if we don't care about the order. If we do care about the order then we have only one option: forEachOrdered().


Attempts like doing some kind of locking or coordination between threads should entirely be avoided.


Examples

Applying side-effect via peek()

If we don't care about order, using peek for printing is ok:

public class SideEffectWithPeek {
   public static void main (String[] args) {
       IntStream.range(0, 5)
                .unordered()
                .parallel()
                .map(x -> x * 2)
                .peek(System.out::println)
                .count();
   }
}


Wrong use of side-effect:

Wrong way to collect pipeline result:

public class SideEffectWrongUse {

    public static void main (String[] args) {
        List<Integer> results = new ArrayList<>();
        IntStream.range(0, 150)
                 .parallel()
                 .filter(s -> s % 2 == 0)
                 .forEach(s -> results.add(s));
        System.out.println(results);
    }
}

Running multiple times may result in this exception:

 Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:598)
	at java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:677)
	at java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:735)
	at java.util.stream.ForEachOps$ForEachOp.evaluateParallel(ForEachOps.java:160)
	at java.util.stream.ForEachOps$ForEachOp$OfInt.evaluateParallel(ForEachOps.java:189)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
	at java.util.stream.IntPipeline.forEach(IntPipeline.java:404)
	at com.logicbig.example.SideEffectWrongUse.main(SideEffectWrongUse.java:13)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.ArrayIndexOutOfBoundsException: 49
	at java.util.ArrayList.add(ArrayList.java:459)
	at com.logicbig.example.SideEffectWrongUse.lambda$main$1(SideEffectWrongUse.java:13)
	at java.util.stream.ForEachOps$ForEachOp$OfInt.accept(ForEachOps.java:205)
	at java.util.stream.IntPipeline$9$1.accept(IntPipeline.java:344)
	at java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Streams.java:110)
	at java.util.Spliterator$OfInt.forEachRemaining(Spliterator.java:693)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
	at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:291)
	at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
	at java.util.concurrent.ForkJoinPool$WorkQueue.execLocalTasks(ForkJoinPool.java:1040)
	at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1058)
	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

Fixing above code:

public class SideEffectWrongUseFix {
    public static void main (String[] args) {
        IntStream stream = IntStream.range(0, 1000);
        List<Integer> list = stream.parallel()
                                      .filter(s -> s % 2 == 0)
                                      .boxed()
                                      .collect(Collectors.toList());
        System.out.println(list);
    }
}


Example Project

Dependencies and Technologies Used:

  • JDK 1.8
  • Maven 3.0.4

Side Effect Examples Select All Download
  • streams-side-effect
    • src
      • main
        • java
          • com
            • logicbig
              • example

See Also