Backend Development 12 min read

Performance Comparison of Java Stream API vs Iterator and Recommendations

This article explains Java 8 Stream fundamentals, compares its intermediate and terminal operations with traditional iterator loops through a series of benchmark tests (mapping, filtering, sorting, reduction, string joining, and mixed operations), analyzes results for different data sizes and CPU configurations, and provides practical guidance on when to use Stream, parallel Stream, or iterator in backend development.

Top Architect
Top Architect
Top Architect
Performance Comparison of Java Stream API vs Iterator and Recommendations

Java 8 introduced the Stream API (java.util.stream) as a new abstraction for processing sequences of elements, offering specialized streams such as IntStream , LongStream , and DoubleStream . Streams replace many collection operations, providing a fluent, functional style with internal iteration.

The article outlines two categories of stream operations: intermediate operations (e.g., filter , distinct , map , sorted ) that return another stream and can be chained, and terminal operations that produce a result or a collection, array, or string.

Key characteristics of streams are highlighted: they can be traversed only once, use internal iteration (generally more efficient than external iterator loops), support lazy evaluation, can represent infinite sequences, and lead to more concise code.

Advantages over traditional collections include no storage of elements (the stream pulls data from the source), functional programming style, lazy evaluation that enables short‑circuiting, ability to handle infinite streams, and reduced boilerplate.

Benchmark setup

System: Ubuntu 16.04 xenial
CPU: Intel Core i7-8550U
RAM: 16GB
JDK version: 1.8.0_151
JVM: HotSpot(TM) 64‑Bit Server VM (build 25.151‑b12, mixed mode)
JVM Settings:
-Xms1024m
-Xmx6144m
-XX:MaxMetaspaceSize=512m
-XX:ReservedCodeCacheSize=1024m
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=100

The following tests were performed on random integer lists with sizes ranging from 10 to 10,000,000, each repeated ten times and averaged:

1. Mapping test

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

2. Filtering test

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

3. Natural sorting test

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

4. Reduction (max) test

// 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();

5. String joining test

// 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(","));

6. Mixed operations test

// 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

For small data sets (size ≤ 1 000), traditional iterator loops are slightly faster than Stream, but the difference is sub‑millisecond and negligible for most business logic; Stream provides much cleaner code. For larger data sets (size > 10 000), Stream—especially parallel Stream—outperforms iterator, leveraging multiple CPU cores for significant speed gains, though parallel Stream’s benefit depends on the actual core allocation and may incur overhead on single‑core machines.

Recommendations

Use iterator for simple, single‑step loops.

Prefer Stream for multi‑step processing to gain readability with minimal performance loss.

Avoid parallel Stream on single‑core environments; use it when multiple cores and large data volumes are available.

When processing primitive streams, convert to IntStream , LongStream , or DoubleStream early to reduce boxing/unboxing overhead.

JavaPerformancebackend-developmentbenchmarkIteratorStream APIParallel Stream
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.