On this page
Stream API
The Stream API (since Java 8) provides a declarative way to process sequences of elements — filtering, mapping, reducing, and collecting — often in a single pipeline.
Creating Streams
// From a Collection
List<String> list = List.of("a", "b", "c");
Stream<String> stream = list.stream();
// From an array
Stream<Integer> intStream = Arrays.stream(new int[]{1, 2, 3});
// Static factory methods
Stream<String> of = Stream.of("x", "y", "z");
Stream<String> empty = Stream.empty();
Stream<Integer> infinite = Stream.iterate(0, n -> n + 1).limit(10);
Stream<Integer> generated = Stream.generate(() -> (int)(Math.random() * 100)).limit(5);
Intermediate Operations
Return a new stream; are lazy until a terminal operation runs.
List<String> names = List.of("Alice", "Bob", "Charlie", "Anna");
List<String> result = names.stream()
.filter(name -> name.startsWith("A")) // Alice, Anna
.map(String::toUpperCase) // ALICE, ANNA
.sorted() // ALICE, ANNA
.toList(); // terminal (Java 16+)
Common intermediate operations:
| Operation | Description |
|---|---|
filter(Predicate) |
Keep elements matching condition |
map(Function) |
Transform each element |
flatMap(Function) |
Flatten nested streams |
distinct() |
Remove duplicates |
sorted() |
Sort elements |
peek(Consumer) |
Observe elements (debugging) |
limit(n) |
Truncate to n elements |
skip(n) |
Skip first n elements |
flatMap Example
List<List<Integer>> nested = List.of(List.of(1, 2), List.of(3, 4));
List<Integer> flat = nested.stream()
.flatMap(List::stream)
.toList(); // [1, 2, 3, 4]
Terminal Operations
Trigger processing and produce a result or side effect.
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
// Collect to list
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.toList(); // [2, 4]
// Reduce
Optional<Integer> sum = numbers.stream()
.reduce((a, b) -> a + b); // 15
// Count
long count = numbers.stream().filter(n -> n > 2).count(); // 3
// Match
boolean allPositive = numbers.stream().allMatch(n -> n > 0); // true
// Find
Optional<Integer> first = numbers.stream()
.filter(n -> n > 3)
.findFirst(); // 4
// forEach
numbers.stream().forEach(System.out::println);
Collectors
import java.util.stream.Collectors;
List<String> words = List.of("apple", "banana", "cherry", "apricot");
// Group by first letter
Map<Character, List<String>> grouped = words.stream()
.collect(Collectors.groupingBy(w -> w.charAt(0)));
// Join strings
String joined = words.stream()
.collect(Collectors.joining(", ")); // apple, banana, cherry, apricot
// Statistics
IntSummaryStatistics stats = words.stream()
.collect(Collectors.summarizingInt(String::length));
System.out.println(stats.getAverage()); // average word length
// Partition
Map<Boolean, List<String>> partition = words.stream()
.collect(Collectors.partitioningBy(w -> w.length() > 5));
Parallel Streams
List<Integer> numbers = IntStream.range(0, 1_000_000)
.boxed()
.toList();
long sum = numbers.parallelStream()
.mapToLong(Integer::longValue)
.sum();
Use parallel streams when:
- Data set is large
- Operations are CPU-intensive and side-effect free
- Source supports efficient splitting (ArrayList, arrays)
Avoid parallel streams for small data sets, I/O-bound tasks, or when order matters.
Best Practices
- Prefer streams for declarative data processing; use loops when logic is complex
- Avoid modifying external state inside stream operations
- Use
toList()(Java 16+) instead ofcollect(Collectors.toList()) - Do not overuse parallel streams — measure before optimizing
- Close streams backed by I/O resources (e.g.,
Files.lines())