Backend Development 11 min read

Why List.sort() Is Faster Than Stream.sorted() in Java

This article investigates the performance difference between Java's native List.sort() method and the Stream.sorted() approach, provides simple demos, explains JIT warm‑up effects, and presents JMH benchmark results that show List.sort() consistently outperforms Stream.sorted() for typical collection sizes.

Top Architect
Top Architect
Top Architect
Why List.sort() Is Faster Than Stream.sorted() in Java

When a comment mentioned list.sort() and list.stream().sorted() , the author wondered why the native list.sort() seemed faster. The article first reproduces a quick demo that measures execution time using System.currentTimeMillis() for both methods on a list of 10,000 random integers.

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");

The output shows stream.sort耗时:62ms and List.sort()耗时:7ms , suggesting the native sort is faster. The author then swaps the order of the two measurements, obtaining List.sort()耗时:68ms and stream.sort耗时:13ms , demonstrating that simple timing with System.currentTimeMillis() is unreliable because of JVM JIT compilation and warm‑up effects.

To obtain reliable data, the article introduces JMH (Java Microbenchmark Harness) and provides a full benchmark class:

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;

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(SortBenchmark.class.getSimpleName())
                .result("SortBenchmark.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 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);
    }
}

Running this benchmark for collection sizes 100, 10,000, and 100,000 confirms that list.sort() consistently has lower average execution time than the stream‑based approach.

The article also explains why the difference exists: the stream pipeline incurs overhead for creating a stream, applying intermediate operations, and collecting the result back into a list, while list.sort() directly invokes the highly optimized native TimSort algorithm. Additionally, the stream version must allocate a new list after sorting, adding extra memory and copy costs.

Further JMH tests (class SortBenchmark3 ) compare the cost of merely converting a list to a stream and collecting it versus performing a full sort via the stream pipeline, showing that the conversion overhead is relatively small but still contributes to the overall slower performance of stream().sorted() .

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);
    }
}

Overall, the article concludes that for most practical scenarios—especially when sorting moderate‑sized collections—the direct list.sort() method is more efficient than converting the list to a stream, sorting, and collecting back, although the convenience of streams may still be preferred when code readability outweighs the minor performance penalty.

JavaPerformanceBenchmarkJMHlist.sortStream.sorted
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

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.