Backend Development 12 min read

Performance Comparison of Java Stream API vs Iterator for Common Operations

This article analyzes Java 8 Stream API versus traditional iterator approaches across mapping, filtering, sorting, reduction, string concatenation and mixed operations, presenting benchmark results on various data sizes and offering practical recommendations for when to use streams, parallel streams, or iterators.

Architect's Tech Stack
Architect's Tech Stack
Architect's Tech Stack
Performance Comparison of Java Stream API vs Iterator for Common Operations

Java 8 introduced the Stream<T> abstraction in java.util.stream , providing a functional style for processing collections, arrays, and other data sources. Each stream represents a sequence of values and offers a rich set of intermediate and terminal operations that can be chained to form a processing pipeline.

Stream Operation Types

Intermediate operations such as filter , distinct , map , and sorted transform the data and always return another stream, allowing multiple operations to be composed.

Terminal operations trigger the actual computation and produce a result, such as a collection, array, or primitive value.

Characteristics of Streams

Streams can be traversed only once; after a pipeline is consumed, a new stream must be created from the source.

Streams use internal iteration, letting the library handle element traversal, which is generally more efficient than external iteration with an Iterator .

Advantages Over Collections

No storage : Streams do not store elements; they pull data from the source on demand.

Functional style : Operations produce new results without modifying the original data source.

Lazy evaluation : Most operations are evaluated lazily, enabling short‑circuiting and reducing unnecessary work.

Unbounded streams : Streams can represent infinite sequences.

Concise code : Stream pipelines often replace verbose iterator loops.

Efficiency Comparison Between Stream and Iterator

Benchmarks were performed on a Ubuntu 16.04 system with an Intel i7‑8550U CPU, 16 GB RAM, and JDK 1.8.0_151. Ten runs were averaged for each data size (10 – 10 000 000 elements).

Mapping Test

Increment each integer in a List<Integer> and collect the results.

// stream
List
result = list.stream()
    .mapToInt(x -> x)
    .map(x -> ++x)
    .boxed()
    .collect(Collectors.toCollection(ArrayList::new));
// iterator
List
result = new ArrayList<>();
for (Integer e : list) {
    result.add(++e);
}
// parallel stream
List
result = list.parallelStream()
    .mapToInt(x -> x)
    .map(x -> ++x)
    .boxed()
    .collect(Collectors.toCollection(ArrayList::new));

Filtering Test

Select elements greater than 200 from a list.

// stream
List
result = list.stream()
    .mapToInt(x -> x)
    .filter(x -> x > 200)
    .boxed()
    .collect(Collectors.toCollection(ArrayList::new));
// iterator
List
result = new ArrayList<>(list.size());
for (Integer e : list) {
    if (e > 200) {
        result.add(e);
    }
}
// parallel stream
List
result = list.parallelStream()
    .mapToInt(x -> x)
    .filter(x -> x > 200)
    .boxed()
    .collect(Collectors.toCollection(ArrayList::new));

Natural Sorting Test

Sort a list of integers using stream and iterator approaches.

// stream
List
result = list.stream()
    .mapToInt(x -> x)
    .sorted()
    .boxed()
    .collect(Collectors.toCollection(ArrayList::new));
// iterator
List
result = new ArrayList<>(list);
Collections.sort(result);
// parallel stream
List
result = list.parallelStream()
    .mapToInt(x -> x)
    .sorted()
    .boxed()
    .collect(Collectors.toCollection(ArrayList::new));

Reduction (Max) Test

Find the maximum value in a list.

// stream
int max = list.stream()
    .mapToInt(x -> x)
    .max()
    .getAsInt();
// iterator
int max = -1;
for (Integer e : list) {
    if (e > max) {
        max = e;
    }
}
// parallel stream
int max = list.parallelStream()
    .mapToInt(x -> x)
    .max()
    .getAsInt();

String Concatenation Test

Join list elements into a comma‑separated string.

// stream
String result = list.stream()
    .map(String::valueOf)
    .collect(Collectors.joining(","));
// iterator
StringBuilder builder = new StringBuilder();
for (Integer e : list) {
    builder.append(e).append(",");
}
String result = builder.length() == 0 ? "" : builder.substring(0, builder.length() - 1);
// parallel stream
String result = list.parallelStream()
    .map(String::valueOf)
    .collect(Collectors.joining(","));

Mixed Operations Test

Combine filtering, mapping (+1), distinct, and collection.

// stream
List
result = list.stream()
    .filter(Objects::nonNull)
    .mapToInt(x -> x + 1)
    .filter(x -> x > 200)
    .distinct()
    .boxed()
    .collect(Collectors.toCollection(ArrayList::new));
// iterator
HashSet
set = new HashSet<>(list.size());
for (Integer e : list) {
    if (e != null && e > 200) {
        set.add(e + 1);
    }
}
List
result = new ArrayList<>(set);
// parallel stream
List
result = list.parallelStream()
    .filter(Objects::nonNull)
    .mapToInt(x -> x + 1)
    .filter(x -> x > 200)
    .distinct()
    .boxed()
    .collect(Collectors.toCollection(ArrayList::new));

Experimental Results Summary

1. For small data sets (≤ 1 000 elements), traditional iterator loops are slightly faster than streams, but the difference is sub‑millisecond and negligible for most business logic; streams provide cleaner code.

2. For large data sets (> 10 000 elements), streams—especially parallel streams—outperform iterators, provided the CPU has multiple cores. Parallel streams leverage the JVM’s ForkJoinPool , but their benefit diminishes on single‑core machines due to overhead.

3. Parallel streams are highly dependent on CPU core availability; without sufficient cores they may be slower than sequential streams.

Recommendations for Using Streams

Use simple iterator loops for straightforward iteration; adopt streams for multi‑step processing to gain readability with minimal performance loss.

Avoid parallelStream() on single‑core CPUs; prefer it on multi‑core systems with large data volumes.

When dealing with boxed types, convert to primitive streams (e.g., mapToInt ) before intermediate operations to reduce boxing/unboxing overhead.

Follow the "搜云库技术团队" WeChat public account for more technical articles and resources.

JavaperformanceBenchmarkIteratorStream APIParallel Stream
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.