Backend Development 11 min read

Why List.sort() Is Faster Than Stream.sorted() in Java: Benchmarks and Analysis

An in‑depth comparison shows that Java's native List.sort() outperforms Stream.sorted() due to lower overhead, demonstrated through simple demos, JMH micro‑benchmarks across various collection sizes, and analysis of stream processing costs, concluding that the performance gap, while measurable, is often negligible in typical use.

Architect's Tech Stack
Architect's Tech Stack
Architect's Tech Stack
Why List.sort() Is Faster Than Stream.sorted() in Java: Benchmarks and Analysis

The article investigates the common claim that List.sort() is faster than stream().sorted() in Java, starting from a comment that highlighted the performance difference without explanation.

First, a simple demo creates a list of 10,000 random integers, sorts it using both approaches, and measures elapsed time with System.currentTimeMillis() . The output shows stream.sort taking about 62 ms while List.sort() takes about 7 ms, suggesting the native sort is faster.

List<Integer> userList = new ArrayList<>();
Random rand = new Random();
for (int i = 0; i < 10000; i++) {
    userList.add(rand.nextInt(1000));
}
List<Integer> userList2 = new ArrayList<>();
userList2.addAll(userList);
Long startTime1 = System.currentTimeMillis();
userList2.stream().sorted(Comparator.comparing(Integer::intValue)).collect(Collectors.toList());
System.out.println("stream.sort耗时:" + (System.currentTimeMillis() - startTime1) + "ms");
Long startTime = System.currentTimeMillis();
userList.sort(Comparator.comparing(Integer::intValue));
System.out.println("List.sort()耗时:" + (System.currentTimeMillis() - startTime) + "ms");

Reversing the order of the two measurements swaps the results, indicating that simple timing is unreliable because of JVM warm‑up and JIT compilation effects.

List<Integer> userList = new ArrayList<>();
Random rand = new Random();
for (int i = 0; i < 10000; i++) {
    userList.add(rand.nextInt(1000));
}
List<Integer> userList2 = new ArrayList<>();
userList2.addAll(userList);
Long startTime = System.currentTimeMillis();
userList.sort(Comparator.comparing(Integer::intValue));
System.out.println("List.sort()耗时:" + (System.currentTimeMillis() - startTime) + "ms");
Long startTime1 = System.currentTimeMillis();
userList2.stream().sorted(Comparator.comparing(Integer::intValue)).collect(Collectors.toList());
System.out.println("stream.sort耗时:" + (System.currentTimeMillis() - startTime1) + "ms");

To obtain reliable data, the author uses JMH (Java Microbenchmark Harness) to benchmark both methods across collection sizes of 100, 10 000, and 100 000 elements. The benchmark class defines two methods: sort (calling arrayList.sort ) and streamSorted (calling arrayList.stream().sorted(...).collect(...) ). Results consistently show List.sort() having lower average time.

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 2, time = 1)
@Measurement(iterations = 5, time = 5)
@Fork(1)
@State(Scope.Thread)
public class SortBenchmark {
    @Param(value = {"100", "10000", "100000"})
    private int operationSize;
    private static List
arrayList;

    @Setup
    public void init() {
        arrayList = new ArrayList<>();
        Random random = new Random();
        for (int i = 0; i < operationSize; i++) {
            arrayList.add(random.nextInt(10000));
        }
    }

    @Benchmark
    public void sort(Blackhole blackhole) {
        arrayList.sort(Comparator.comparing(e -> e));
        blackhole.consume(arrayList);
    }

    @Benchmark
    public void streamSorted(Blackhole blackhole) {
        arrayList = arrayList.stream().sorted(Comparator.comparing(e -> e)).collect(Collectors.toList());
        blackhole.consume(arrayList);
    }
}

The author explains why the overhead exists: converting a list to a stream, applying sorted , and then collecting back to a list incurs additional object creation and traversal, while List.sort() directly invokes the highly optimized native sort algorithm.

Further micro‑benchmarks isolate the cost of merely converting a collection to a stream and back, confirming that this step adds measurable time but remains a small fraction of the total sorting cost.

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 2, time = 1)
@Measurement(iterations = 5, time = 5)
@Fork(1)
@State(Scope.Thread)
public class SortBenchmark3 {
    @Param(value = {"100", "10000"})
    private int operationSize; // 操作次数
    private static List
arrayList;

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(SortBenchmark3.class.getSimpleName())
                .result("SortBenchmark3.json")
                .mode(Mode.All)
                .resultFormat(ResultFormatType.JSON)
                .build();
        new Runner(opt).run();
    }

    @Setup
    public void init() {
        arrayList = new ArrayList<>();
        Random random = new Random();
        for (int i = 0; i < operationSize; i++) {
            arrayList.add(random.nextInt(10000));
        }
    }

    @Benchmark
    public void stream(Blackhole blackhole) {
        arrayList.stream().collect(Collectors.toList());
        blackhole.consume(arrayList);
    }

    @Benchmark
    public void sort(Blackhole blackhole) {
        arrayList.stream().sorted(Comparator.comparing(Integer::intValue)).collect(Collectors.toList());
        blackhole.consume(arrayList);
    }
}

Results show that while the stream conversion adds overhead, the dominant factor is the extra sorting step performed on the stream, which internally still uses the native sort algorithm; therefore the total time for stream().sorted() is inevitably higher.

Finally, the article notes that the discussion concerns sequential streams (not parallel streams) and that for most real‑world scenarios with hundreds to thousands of elements, the difference is minor and developers should choose the API that yields clearer code.

JavaPerformanceBenchmarkJMHlist.sortStream.sorted
Architect's Tech Stack
Written by

Architect's Tech Stack

Java backend, microservices, distributed systems, containerized programming, and more.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.