You need to use forEachOrdered, not forEach.

As per the forEach doc:

For parallel stream pipelines, this operation does not guarantee to respect the encounter order of the stream, as doing so would sacrifice the benefit of parallelism. For any given element, the action may be performed at whatever time and in whatever thread the library chooses. If the action accesses shared state, it is responsible for providing the required synchronization.

Answer from Louis Wasserman on Stack Overflow
Top answer
1 of 3
62

You need to use forEachOrdered, not forEach.

As per the forEach doc:

For parallel stream pipelines, this operation does not guarantee to respect the encounter order of the stream, as doing so would sacrifice the benefit of parallelism. For any given element, the action may be performed at whatever time and in whatever thread the library chooses. If the action accesses shared state, it is responsible for providing the required synchronization.

2 of 3
8

In addition, you can read more about parallelism and forEachOrdered with a very nice example from here. In summary, using forEachOrdered in a parallel stream might result to lose the benefits of parallelism.

Here the example from the same resource:

Integer[] intArray = {1, 2, 3, 4, 5, 6, 7, 8 };
List<Integer> listOfIntegers =
    new ArrayList<>(Arrays.asList(intArray));

System.out.println("listOfIntegers:");
listOfIntegers
    .stream()
    .forEach(e -> System.out.print(e + " "));
System.out.println("");

System.out.println("listOfIntegers sorted in reverse order:");
Comparator<Integer> normal = Integer::compare;
Comparator<Integer> reversed = normal.reversed(); 
Collections.sort(listOfIntegers, reversed);  
listOfIntegers
    .stream()
    .forEach(e -> System.out.print(e + " "));
System.out.println("");

System.out.println("Parallel stream");
listOfIntegers
    .parallelStream()
    .forEach(e -> System.out.print(e + " "));
System.out.println("");

System.out.println("Another parallel stream:");
listOfIntegers
    .parallelStream()
    .forEach(e -> System.out.print(e + " "));
System.out.println("");

System.out.println("With forEachOrdered:");
listOfIntegers
    .parallelStream()
    .forEachOrdered(e -> System.out.print(e + " "));
System.out.println("");

And the output is

listOfIntegers:
1 2 3 4 5 6 7 8
listOfIntegers sorted in reverse order:
8 7 6 5 4 3 2 1
Parallel stream:
3 4 1 6 2 5 7 8
Another parallel stream:
6 3 1 5 7 8 4 2
With forEachOrdered:
8 7 6 5 4 3 2 1

The fifth pipeline uses the method forEachOrdered, which processes the elements of the stream in the order specified by its source, regardless of whether you executed the stream in serial or parallel. Note that you may lose the benefits of parallelism if you use operations like forEachOrdered with parallel streams

.

🌐
DZone
dzone.com › coding › java › parallel sort
Parallel Sort - Java
September 18, 2023 - Arrays and Lists are the popular data structures that benefited from parallel sorting, and they directly started supporting it. However, prior to Java 8, parallelism is not directly supported. Java has provided parallelism support in the Stream API. For example, java.util.List has a stream() method that will support parallelism.
🌐
Oracle
docs.oracle.com › javase › 8 › docs › api › java › util › stream › Stream.html
Stream (Java Platform SE 8 )
3 weeks ago - If consistency with encounter order ... execution with BaseStream.sequential() may improve performance. ... Returns a stream consisting of the elements of this stream, sorted according to natural order. If the elements of this stream are not Comparable, a java.lang.ClassCas...
🌐
Oracle
docs.oracle.com › javase › tutorial › collections › streams › parallelism.html
Parallelism (The Java™ Tutorials > Collections > Aggregate Operations)
The operation forEachOrdered processes elements in the order specified by the stream, regardless of whether the stream is executed in serial or parallel. However, when a stream is executed in parallel, the map operation processes elements of the stream specified by the Java runtime and compiler.
🌐
Baeldung
baeldung.com › home › java › java streams › stream ordering in java
Stream Ordering in Java
January 8, 2024 - If our Stream is ordered, it doesn’t matter whether our data is being processed sequentially or in parallel; the implementation will maintain the encounter order of the Stream.
🌐
Blogger
javarevisited.blogspot.com › 2020 › 08 › java-8-parallelstream-example.html
Java 8 Parallel Stream Example? How to improve performance by using parallelStream() in Java?
// sequential sorting long count = values.stream().sorted().count(); // parallel sorting long count1 = values.parallelStream().sorted().count(); // Output 10000000 sequential sorting of 10000000 integers took: 7346 ms 10000000 parallel sorting of 10000000 integers took: 2435 ms You can see the parallel version of the code is almost 3 times faster than the sequential ones and you achieve that my just using parallelStream() method instead of stream(), I bet it can get more simpler than this unless you ask ChatGPT to sort a list for you. Also, Here is a nice diagram which explains how parallel stream works in Java:
🌐
Ionutbalosin
ionutbalosin.com › 2018 › 05 › parallel-streams-behaves-sequentially-up-to-a-certain-extend
Parallel streams behave sequentially up to a certain extent – Ionut Balosin
May 18, 2018 - Let’s jump into the JDK sources inside the java.util.Arrays.java class: public static <T extends Comparable<? super T>> void parallelSort(T[] a) { int n = a.length; if n <= 1 << 13 // sequencial sort else // parallel sort } ... If the array length is below a certain granularity (e.g. MIN_ARRAY_SORT_GRAN = 1 << 13 which corresponds to 8192), the array is not partitioned anymore and is sequentially sorted using Arrays.sort(), even if at the code level the programmer explicitly requires a parallel stream!
🌐
Medium
medium.com › @AlexanderObregon › javas-stream-sorted-method-explained-52b9b25e9f84
Java’s Stream.sorted() Method Explained | Medium
December 26, 2024 - However, parallel streams may not always provide benefits depending on the data size and system configuration. The Stream.sorted() method in Java offers a dependable way to sort data streams using natural order or custom criteria.
Find elsewhere
🌐
Fast thread
blog.fastthread.io › home › parallel sort
Parallel Sort - Fast thread
August 31, 2025 - Java 8 introduced parallel sorting through the Streams API, enhancing efficiency using multithreading. Performance benchmarks demonstrate significant time savings with parallel sorting.
🌐
Javastudyguide
ocpj8.javastudyguide.com › ch18.html
Java 8 Programmer II Study Guide: Exam 1Z0-809
One might think that the stream is sorted and filtered sequentially, but the output shows something else: Filter:c Map:c cc Filter:a Map:a aa Filter:b Map:b bb Filter:d Filter:e Map:e ee 79.470779 milliseconds · Compare this with the output of the sequential version (just comment parallel()):
Top answer
1 of 2
66

TL;DR

Yes, the order is guaranteed.

Stream.collect() API documentation

The starting place is to look at what determines whether a reduction is concurrent or not. Stream.collect()'s description says the following:

If the stream is parallel, and the Collector is concurrent, and either the stream is unordered or the collector is unordered, then a concurrent reduction will be performed (see Collector for details on concurrent reduction.)

The first condition is satisfied: the stream is parallel. How about the second and third: is the Collector concurrent and unordered?
 

Collectors.toList() API documentation

toList()'s documentation reads:

Returns a Collector that accumulates the input elements into a new List. There are no guarantees on the type, mutability, serializability, or thread-safety of the List returned; if more control over the returned List is required, use toCollection(Supplier).

Returns:
a Collector which collects all the input elements into a List, in encounter order

An operation that works in encounter order operates on the elements in their original order. This overrides parallelness.
 

Implementation code

Inspecting the implementation of Collectors.java confirms that toList() does not include the CONCURRENT or UNORDERED traits.

public static <T>
Collector<T, ?, List<T>> toList() {
    return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
                               (left, right) -> { left.addAll(right); return left; },
                               CH_ID);
}

// ...

static final Set<Collector.Characteristics> CH_ID
        = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));

Notice how the collector has the CH_ID trait set, which has only the single IDENTITY_FINISH trait. CONCURRENT and UNORDERED are not there, so the reduction cannot be concurrent.

A non-concurrent reduction means that, if the stream is parallel, collection can proceed in parallel, but it will be split into several thread-confined intermediate results which are then combined. This ensures the combined result is in encounter order.
 

See also: Why parallel stream get collected sequentially in Java 8

2 of 2
7

You are guaranteed to get the elements in encounter order.

From documentation of toList:

Returns: a Collector which collects all the input elements into a List, in encounter order

See java.util.streams summary for more information on the term "encounter order".

Furthermore, List#spliterator documentation requires that the all implementations of List produce spliterators that are ORDERED:

The Spliterator reports Spliterator.SIZED and Spliterator.ORDERED. Implementations should document the reporting of additional characteristic values.

Oddly enough, while List interface requires iterator() to produce elements in "proper sequence", spliterator() is only required to be ordered but not specifically required to follow the list's natural ordering.

So, to answer your question, the list produced by toList is guaranteed to contain the elements exactly as the source list's spliterator orders them. It does not matter whether the stream is parallel or sequential.

🌐
GeeksforGeeks
geeksforgeeks.org › java › what-is-java-parallel-streams
What is Java Parallel Streams? - GeeksforGeeks
February 21, 2025 - Therefore, it is advisable to use parallel streams in cases where no matter what is the order of execution, the result is unaffected and the state of one element does not affect the other as well as the source of the data also remains unaffected. Parallel streams are best used when the order doesn’t matter, elements don’t depend on each other, and data remains unchanged.
Top answer
1 of 2
3

I assume that your problem is purely educational and experimental without any practical application as there are much more efficient ways to sort elements in Java. If you want to utilize the Stream API here, you may create a spliterator which performs bubble sorting and collector which performs merge sorting in combiner.

Here's spliterator:

static class BubbleSpliterator<T> implements Spliterator<T> {
    private final Comparator<? super T> cmp;
    private final Spliterator<T> source;
    private T[] data;
    private int offset;

    public BubbleSpliterator(Spliterator<T> source, Comparator<? super T> cmp) {
        this.source = source;
        this.cmp = cmp;
    }

    @SuppressWarnings("unchecked")
    private void init() {
        if (data != null)
            return;
        Stream.Builder<T> buf = Stream.builder();
        source.forEachRemaining(buf);
        data = (T[]) buf.build().toArray();
        bubble(data, cmp);
    }

    private static <T> void bubble(T[] data, Comparator<? super T> cmp) {
        for (int i = 0; i < data.length - 1; i++)
            for (int j = i + 1; j < data.length; j++) {
                if (cmp.compare(data[i], data[j]) > 0) {
                    T tmp = data[i];
                    data[i] = data[j];
                    data[j] = tmp;
                }
            }
    }

    @Override
    public boolean tryAdvance(Consumer<? super T> action) {
        init();
        if (offset >= data.length)
            return false;
        action.accept(data[offset++]);
        return true;
    }

    @Override
    public void forEachRemaining(Consumer<? super T> action) {
        init();
        for (int i = offset; i < data.length; i++)
            action.accept(data[i]);
        offset = data.length;
    }

    @Override
    public Spliterator<T> trySplit() {
        if (data != null)
            return null;
        Spliterator<T> prefix = source.trySplit();
        return prefix == null ? null : new BubbleSpliterator<>(prefix, cmp);
    }

    @Override
    public long estimateSize() {
        if (data != null)
            return data.length - offset;
        return source.estimateSize();
    }

    @Override
    public int characteristics() {
        return source.characteristics();
    }

    public static <T> Stream<T> stream(Stream<T> source, 
                                       Comparator<? super T> comparator) {
        Spliterator<T> spltr = source.spliterator();
        return StreamSupport.stream(new BubbleSpliterator<>(spltr, comparator), 
               source.isParallel()).onClose(source::close);
    }
}

It takes source, delegates splitting to the source, but when elements are requested it dumps the source elements to array and performs a bubble sorting for them. You may check it like this:

int[] data = new Random(1).ints(100, 0, 1000).toArray();
Comparator<Integer> comparator = Comparator.naturalOrder();
List<Integer> list = BubbleSpliterator.stream(Arrays.stream(data).parallel().boxed(), comparator).collect(
    Collectors.toList());
System.out.println(list);

The result depends on number of hardware threads on your machine and may look like this:

[254, 313, 588, 847, 904, 985, 434, 473, 569, 606, 748, 978, 234, 262, 263, 317, 562, 592, 99, 189, 310,...]

Here you can see that output consists of several sorted sequences. The number of such sequences corresponds to the number of parallel tasks Stream API creates.

Now to combine sorted sequences via merge sorting you may write a special collector like this:

static <T> List<T> merge(List<T> l1, List<T> l2, Comparator<? super T> cmp) {
    List<T> result = new ArrayList<>(l1.size()+l2.size());
    int i=0, j=0;
    while(i < l1.size() && j < l2.size()) {
        if(cmp.compare(l1.get(i), l2.get(j)) <= 0) {
            result.add(l1.get(i++));
        } else {
            result.add(l2.get(j++));
        }
    }
    result.addAll(l1.subList(i, l1.size()));
    result.addAll(l2.subList(j, l2.size()));
    return result;
}

static <T> Collector<T, ?, List<T>> mergeSorting(Comparator<? super T> cmp) {
    return Collector.<T, List<T>> of(ArrayList::new, List::add, 
                                     (l1, l2) -> merge(l1, l2, cmp));
}

In sequential more it works just like the Collectors.toList(), but in parallel it performs merge sorting assuming that both input lists are already sorted. My mergeSorting implementation is probably suboptimal, you may write something better.

So to sort everything via Stream API, you can use BubbleSpliterator and mergeSorting collector together:

int[] data = new Random(1).ints(100, 0, 1000).toArray();
Comparator<Integer> comparator = Comparator.naturalOrder();
List<Integer> list = BubbleSpliterator.stream(Arrays.stream(data).parallel().boxed(), comparator).collect(
    mergeSorting(comparator));
System.out.println(list);

The result will be completely sorted.

This implementation performs unnecessary copying of input data several times, so I guess, custom bubble+merge implementation could beat this one in terms of performance.

2 of 2
1

if you want to sort an array in parallel using Java 8 Stream API, this may help you :

IntStream randomIntegers = ThreadLocalRandom.current().ints(100, 0, 100);
int[] sortedArray = randomIntegers
        .parallel() // (1)
        .sorted() // (2)
        .toArray();
System.out.println(Arrays.toString(sortedArray));

no matter what type of Stream you have, just invoke parallel() and then sorted(). (the order of invocation is not important)

by tracing the code we find that :

final class SortedOps {

    private static final class OfInt extends IntPipeline.StatefulOp<Integer> {
        //...

        @Override
        public <P_IN> Node<Integer> opEvaluateParallel(PipelineHelper<Integer> helper,
                                                       Spliterator<P_IN> spliterator,
                                                       IntFunction<Integer[]> generator) {
            if (StreamOpFlag.SORTED.isKnown(helper.getStreamAndOpFlags())) {
                return helper.evaluate(spliterator, false, generator);
            } else {
                Node.OfInt n = (Node.OfInt) helper.evaluate(spliterator, true, generator);

                int[] content = n.asPrimitiveArray();
                Arrays.parallelSort(content); // <== this

                return Nodes.node(content);
            }
        }
    }

    private static final class OfRef<T> extends ReferencePipeline.StatefulOp<T, T> {
        //...

        @Override
        public <P_IN> Node<T> opEvaluateParallel(PipelineHelper<T> helper,
                                                 Spliterator<P_IN> spliterator,
                                                 IntFunction<T[]> generator) {
            // If the input is already naturally sorted and this operation
            // naturally sorts then collect the output
            if (StreamOpFlag.SORTED.isKnown(helper.getStreamAndOpFlags()) && isNaturalSort) {
                return helper.evaluate(spliterator, false, generator);
            }
            else {
                // @@@ Weak two-pass parallel implementation; parallel collect, parallel sort
                T[] flattenedData = helper.evaluate(spliterator, true, generator).asArray(generator);
                Arrays.parallelSort(flattenedData, comparator); // <== this
                return Nodes.node(flattenedData);
            }
        }
    }

}

Arrays.parallelSort() is used to sort the backing array.

🌐
Medium
hkdemircan.medium.com › java8-concurrency-parallel-stream-88b4aef91a56
Java8 | Concurrency-Parallel Stream | by Hasan Kadir Demircan | Medium
April 7, 2023 - What should we pay attention to in parallel stream? Arrays.asList("jackal", "kangaroo", "lemur").parallelStream().map(s -> { System.out.println(s); return s.toUpperCase(); }).forEach(System.out::println); For the exam, you should remember that parallel streams can process results.
Top answer
1 of 1
16

It looks like Arrays.parallelSort isn't stable in some circumstances. Well spotted. The stream parallel sort is implemented in terms of Arrays.parallelSort, so it affects streams as well. Here's a simplified example:

public class StableSortBug {
    static final int SIZE = 50_000;

    static class Record implements Comparable<Record> {
        final int sortVal;
        final int seqNum;

        Record(int i1, int i2) { sortVal = i1; seqNum = i2; }

        @Override
        public int compareTo(Record other) {
            return Integer.compare(this.sortVal, other.sortVal);
        }
    }

    static Record[] genArray() {
        Record[] array = new Record[SIZE];
        Arrays.setAll(array, i -> new Record(i / 10_000, i));
        return array;
    }

    static boolean verify(Record[] array) {
        return IntStream.range(1, array.length)
                        .allMatch(i -> array[i-1].seqNum + 1 == array[i].seqNum);
    }

    public static void main(String[] args) {
        Record[] array = genArray();
        System.out.println(verify(array));
        Arrays.sort(array);
        System.out.println(verify(array));
        Arrays.parallelSort(array);
        System.out.println(verify(array));
    }
}

On my machine (2 core x 2 threads) this prints the following:

true
true
false

Of course, it's supposed to print true three times. This is on the current JDK 9 dev builds. I wouldn't be surprised if it occurs in all the JDK 8 releases thus far, given what you've tried. Curiously, reducing the size or the divisor will change the behavior. A size of 20,000 and a divisor of 10,000 is stable, and a size of 50,000 and a divisor of 1,000 is also stable. It seems like the problem has to do with a sufficiently large run of values comparing equal versus the parallel split size.

The OpenJDK issue JDK-8076446 covers this bug.

🌐
GeeksforGeeks
geeksforgeeks.org › java › java-8-arrays-parallelsort-method-with-examples
Arrays.parallelSort() in Java with Examples - GeeksforGeeks
July 11, 2025 - For iteration number: 1 Serial Sort Time: 311945 ns Parallel Sort Time: 45702 ns For iteration number: 2 Serial Sort Time: 58249 ns Parallel Sort Time: 11921 ns For iteration number: 3 Serial Sort ...
🌐
Medium
medium.com › @AlexanderObregon › javas-stream-foreachordered-explained-170e6a75c235
Java’s Stream.forEachOrdered() Explained | Medium
September 17, 2024 - This output occurs because the elements are processed by multiple threads concurrently, and there is no guarantee of the original order. The advantage of this behavior is faster performance in parallel streams, particularly when processing large ...
🌐
Baeldung
baeldung.com › home › java › java streams › when to use a parallel stream in java
When to Use a Parallel Stream in Java | Baeldung
November 10, 2025 - Java 8 introduced the Stream API that makes it easy to iterate over collections as streams of data. It’s also very easy to create streams that execute in parallel and make use of multiple processor cores.
🌐
HowToDoInJava
howtodoinjava.com › home › java 8 › java stream foreachordered()
Java Stream forEachOrdered() with Examples - HowToDoInJava
March 15, 2022 - List<Integer> list = Arrays.asList(2, 4, 6, 8, 10); list.stream().parallel().forEach( System.out::println ); //1 list.stream().parallel().forEachOrdered( System.out::println ); //2 ... List<Integer> list = Arrays.asList(2, 4, 6, 8, 10); list.stream() .forEachOrdered( System.out::println ); Program output. ... List<Integer> list = Arrays.asList(2, 4, 6, 8, 10); list.stream() .sorted(Comparator.reverseOrder()) .forEachOrdered(System.out::println); Program output. ... Drop me your questions related to the Stream forEachOrdered() method in Java Stream API.