Java Stream API & Collectors —
Exhaustive Reference with Examples
Everything you need for interviews and real-world coding: each Stream/Collectors method with
purpose, example, and corner cases. Common/most useful APIs are highlighted in green.
Quick Contents
• 1. Streams: Concepts & Building Blocks
• 2. Stream Sources (creation)
• 3. Intermediate Operations
• 4. Terminal Operations
• 5. Primitive Streams (IntStream, LongStream, DoubleStream)
• 6. Optional & OptionalX from streams
• 7. Parallel Streams & Spliterators (internals)
• 8. Collectors — complete catalog
• 9. Corner Cases & Gotchas
• 10. Practical Patterns & Recipes
1) Streams: Concepts & Building Blocks
• Pipelines: Source → Intermediate ops (zero or more) → Terminal op (one).
• Laziness: intermediate ops do not run until a terminal op is invoked.
• Non-mutating by design: operate on views, not in-place mutation.
• Base interfaces: BaseStream<T,S>, Stream<T>, IntStream, LongStream, DoubleStream.
// A small pipeline
List<String> out = names.stream() // Source
.filter(n -> n.length() > 3) // Intermediate
(lazy)
.map(String::toUpperCase)
.sorted()
.toList(); // Terminal
(executes)
Most useful daily combo: filter → map → collect(toList()).
2) Stream Sources (Creation)
Stream.empty()
Purpose: Empty stream.
Stream<String> s = Stream.<String>empty();
Corner case / tip: Useful for composing APIs without nulls.
Stream.of(T...)
Purpose: Varargs of elements.
Stream<Integer> s = Stream.of(1,2,3);
Corner case / tip: Beware of Stream.of(array) vs Arrays.stream(array).
Stream.ofNullable(T)
Purpose: 0 or 1 element depending on null.
Stream<String> s = Stream.ofNullable(System.getenv("USER"));
Corner case / tip: Great for null-safe pipelines (JDK 9+).
Stream.generate(Supplier)
Purpose: Infinite stream; use limit.
Stream<Double> r = Stream.generate(Math::random).limit(3);
Corner case / tip: Make sure to limit to avoid infinite processing.
Stream.iterate(seed, f)
Purpose: Infinite sequence.
Stream<Integer> evens = Stream.iterate(0, x->x+2).limit(5);
Corner case / tip: Use iterate(seed, hasNext, next) to bound.
Stream.iterate(seed, hasNext, next)
Purpose: Finite, bounded iteration.
Stream<Integer> s = Stream.iterate(0, x->x<10, x->x+3);
Corner case / tip: Stops when predicate fails (JDK 9+).
Collection.stream()
Purpose: From collections.
list.stream();
Corner case / tip: ArrayList streams split well in parallel.
Collection.parallelStream()
Purpose: Parallel stream from collection.
list.parallelStream();
Corner case / tip: Use with care; see parallel section.
Arrays.stream(array)
Purpose: Array to stream.
int sum = Arrays.stream(new int[]{1,2,3}).sum();
Corner case / tip: Prefer primitive specializations to avoid boxing.
Files.lines(Path)
Purpose: Lines of a file (lazy, must close).
try (Stream<String> lines = Files.lines(path)) { lines.forEach(...);}
Corner case / tip: Use try-with-resources to close.
BufferedReader.lines()
Purpose: Lines from reader.
try (var br = Files.newBufferedReader(path))
{ br.lines().forEach(...);}
Corner case / tip: Closes with reader.
Pattern.splitAsStream(CharSequence)
Purpose: Split string into stream.
Pattern.compile(",").splitAsStream("a,b,c").forEach(...);
Corner case / tip: Good for large text parsing.
Stream.builder()
Purpose: Manually build stream.
Stream<String> s =
Stream.<String>builder().add("a").add("b").build();
Corner case / tip: Single-use builder.
3) Intermediate Operations
filter(Predicate)
Purpose: Keep elements that satisfy predicate.
List<String> longOnes = words.stream().filter(w -> w.length() >=
5).toList();
Corner case / tip: Stateless, short-circuits per element only with terminal match ops.
Common
map(Function)
Purpose: Transform each element.
List<Integer> lens = words.stream().map(String::length).toList();
Corner case / tip: Avoid heavy allocations; consider mapToInt for primitives.
Common
mapToInt/mapToLong/mapToDouble
Purpose: Project to primitive streams.
int sum = words.stream().mapToInt(String::length).sum();
Corner case / tip: Eliminates boxing; enables numeric ops.
flatMap(Function<T,Stream<R>>)
Purpose: Flatten one level of streams.
List<String> tags = posts.stream().flatMap(p ->
p.tags().stream()).toList();
Corner case / tip: Do not return null; return Stream.empty() for no elements.
Common
flatMapToInt/Long/Double
Purpose: Flatten to primitive streams.
int total = paragraphs.stream().flatMapToInt(s -> s.chars()).sum();
Corner case / tip: Good for char/byte processing.
mapMulti(BiConsumer<T,Consumer<R>>) (JDK 16+)
Purpose: Emit 0..N results without creating intermediate streams.
Stream.<List<Integer>>of(List.of(1,2), List.of(3))
.mapMulti((lst, sink) -> lst.forEach(sink))
.forEach(System.out::println);
Corner case / tip: Faster than flatMap when you can push directly to sink.
distinct()
Purpose: Remove duplicates using equals/hashCode.
List<Integer> uniq = nums.stream().distinct().toList();
Corner case / tip: Mutable elements can break equals/hashCode; avoid mutating after distinct.
Common
sorted()
Purpose: Natural order sort.
List<String> s = words.stream().sorted().toList();
Corner case / tip: Requires Comparable or use Comparator overload.
Common
sorted(Comparator)
Purpose: Custom comparator.
var s =
words.stream().sorted(Comparator.comparingInt(String::length)).toList
();
Corner case / tip: Avoid inconsistent comparators (must be transitive).
Common
peek(Consumer)
Purpose: Debug/observe elements.
List<String> out = words.stream().peek(System.out::println).toList();
Corner case / tip: Does nothing without terminal op; avoid side effects in production.
limit(long)
Purpose: Take first n elements.
var first10 = stream.limit(10).toList();
Corner case / tip: On unordered parallel streams, may pull any n elements.
Common
skip(long)
Purpose: Skip first n elements.
var tail = stream.skip(5).toList();
Corner case / tip: Combined with limit for pagination.
Common
takeWhile(Predicate) (JDK 9+)
Purpose: Take prefix until predicate fails.
Stream.of(1,2,2,3,1).takeWhile(x->x<=2).toList(); // [1,2,2]
Corner case / tip: Works on encounter order; not a global filter.
Common
dropWhile(Predicate) (JDK 9+)
Purpose: Drop prefix while predicate holds.
Stream.of(1,1,2,3).dropWhile(x->x==1).toList(); // [2,3]
Corner case / tip: Complements takeWhile.
Common
boxed()
Purpose: Convert primitive stream to boxed Stream<T>.
Stream<Integer> s = IntStream.range(0,3).boxed();
Corner case / tip: Avoid if not needed; introduces boxing.
sequential()/parallel()
Purpose: Switch execution mode.
list.stream().parallel().map(...).sequential().forEach(...);
Corner case / tip: Parallel can hurt if tasks are small/I-O bound.
unordered()
Purpose: Drop ordering constraints (hint).
long count = list.stream().unordered().distinct().count();
Corner case / tip: Enables optimizations for some sources.
onClose(Runnable)
Purpose: Register close handler.
try (var s = Files.lines(path).onClose(() ->
System.out.println("closed"))) { s.count(); }
Corner case / tip: Runs when stream is closed; multiple onClose accumulate.
4) Terminal Operations
forEach(Consumer)
Purpose: Consume elements in arbitrary order (unless ordered source).
words.stream().forEach(System.out::println);
Corner case / tip: In parallel, order not guaranteed; use forEachOrdered for ordering.
forEachOrdered(Consumer)
Purpose: Consume in encounter order.
words.parallelStream().forEachOrdered(System.out::println);
Corner case / tip: May reduce parallel performance vs forEach.
toArray()
Purpose: Materialize elements to Object[].
Object[] arr = stream.toArray();
Corner case / tip: Prefer typed array with generator.
toArray(IntFunction<A[]>)
Purpose: Materialize to typed array.
String[] arr = words.stream().toArray(String[]::new);
Corner case / tip: Avoid casting from Object[].
reduce(identity, accumulator)
Purpose: Fold with identity and accumulator.
int sum = nums.stream().reduce(0, Integer::sum);
Corner case / tip: Accumulator must be associative; identity should be neutral element.
Common
reduce(accumulator)
Purpose: Fold without identity → Optional<T>.
Optional<Integer> sum = nums.stream().reduce(Integer::sum);
Corner case / tip: Empty stream → Optional.empty().
Common
reduce(identity, accumulator, combiner)
Purpose: Parallel-friendly fold.
int sum = nums.parallelStream().reduce(0, Integer::sum,
Integer::sum);
Corner case / tip: Combiner merges partials; must be associative.
Common
collect(Collector)
Purpose: General reduction using Collector.
Map<String, Long> freq =
words.stream().collect(Collectors.groupingBy(w->w,
Collectors.counting()));
Corner case / tip: Prefer collectors for complex aggregations.
Common
collect(supplier,accumulator,combiner)
Purpose: Custom mutable reduction.
StringBuilder sb = words.stream().collect(StringBuilder::new, (b,w)-
>b.append(w), StringBuilder::append);
Corner case / tip: Ensure thread-safe combiner for parallel.
Common
min(Comparator)
Purpose: Minimum by comparator.
Optional<String> s =
words.stream().min(Comparator.comparingInt(String::length));
Corner case / tip: Empty stream → Optional.empty().
max(Comparator)
Purpose: Maximum by comparator.
Optional<String> s =
words.stream().max(Comparator.comparingInt(String::length));
Corner case / tip: Same Optional behavior as min.
count()
Purpose: Element count.
long c = stream.count();
Corner case / tip: Consumes the stream; cannot reuse.
anyMatch/allMatch/noneMatch(Predicate)
Purpose: Short-circuiting match operations.
boolean ok = nums.stream().anyMatch(n -> n > 100);
Corner case / tip: Respect encounter order; stop early when possible.
Common
findFirst()
Purpose: First element in encounter order.
Optional<String> first = stream.findFirst();
Corner case / tip: On unordered sources, still deterministic for sequential streams.
Common
findAny()
Purpose: Any element (may be faster on parallel).
Optional<String> any = stream.parallel().findAny();
Corner case / tip: For sequential ordered streams, behaves like findFirst.
toList() (JDK 16+)
Purpose: Collect into unmodifiable list.
List<String> list = stream.toList();
Corner case / tip: Adding to result throws UnsupportedOperationException.
Common
5) Primitive Streams (IntStream, LongStream, DoubleStream)
Why use them? Eliminate boxing and unlock numeric helpers.
range(a,b) / rangeClosed(a,b)
Purpose: Generate sequences.
IntStream r = IntStream.range(0, 5); // 0..4
Corner case / tip: Prefer rangeClosed when inclusive upper bound is desired.
sum() / average() / min() / max()
Purpose: Numeric reducers.
double avg = IntStream.of(1,2,3).average().orElse(0.0);
Corner case / tip: average returns OptionalDouble (watch empties).
summaryStatistics()
Purpose: Count, sum, min, max, avg in one object.
IntSummaryStatistics s = IntStream.of(1,2,3).summaryStatistics();
Corner case / tip: Great for reporting.
map/mapToObj/flatMap
Purpose: Transformations.
Stream<Character> chars = "abc".chars().mapToObj(c -> (char)c);
Corner case / tip: chars() yields an IntStream of code points.
boxed()
Purpose: Convert to Stream<Integer/Long/Double>.
Stream<Integer> s = IntStream.range(0,3).boxed();
Corner case / tip: Only box when necessary.
asLongStream()/asDoubleStream()
Purpose: Widening conversions.
DoubleStream ds = IntStream.of(1,2).asDoubleStream();
Corner case / tip: Precision follows double rules.
Common: range/rangeClosed, sum/average, summaryStatistics.
6) Optionals from Streams
• Generated by: findFirst/findAny/min/max/reduce(without identity)/average (primitive).
Optional<String> first = words.stream().filter(w->!
w.isBlank()).findFirst();
String value = first.orElse("N/A");
• APIs: orElse, orElseGet, orElseThrow, ifPresent, ifPresentOrElse, map, flatMap, filter.
• Primitive variants: OptionalInt/Long/Double.
7) Parallel Streams & Spliterators (Internals)
• Good for CPU-bound tasks over large datasets where operations are associative and
independent.
• Avoid with I/O-bound tasks or shared mutable state.
Map<String, Long> freq = words.parallelStream()
.collect(Collectors.groupingByConcurrent(w -> w,
Collectors.counting()));
• Spliterator characteristics: ORDERED, DISTINCT, SORTED, SIZED, NONNULL, IMMUTABLE,
CONCURRENT, SUBSIZED.
• Sources that split well: ArrayList, arrays; Poor: LinkedList, I/O-backed.
8) Collectors — Complete Catalog
toList()
Purpose: Collect to a modifiable list (implementation-specific). Prefer Stream.toList() for
unmodifiable.
List<String> list = stream.collect(Collectors.toList());
Corner case / tip: May not be unmodifiable; use Stream.toList() (JDK 16+) or
toUnmodifiableList().
Common
toUnmodifiableList()
Purpose: Unmodifiable list.
List<String> list = stream.collect(Collectors.toUnmodifiableList());
Corner case / tip: Adding throws UnsupportedOperationException.
toSet()
Purpose: Collect to a modifiable set.
Set<String> set = stream.collect(Collectors.toSet());
Corner case / tip: Order unspecified; may lose duplicates.
toUnmodifiableSet()
Purpose: Unmodifiable set.
Set<String> set = stream.collect(Collectors.toUnmodifiableSet());
Corner case / tip: Unmodifiable result.
toCollection(Supplier<C>)
Purpose: Specify concrete collection type.
LinkedHashSet<String> lhs =
stream.collect(Collectors.toCollection(LinkedHashSet::new));
Corner case / tip: Use for predictable order or special collections.
joining()
Purpose: Concatenate CharSequence elements with no delimiter.
String s = Stream.of("a","b").collect(Collectors.joining());
Corner case / tip: Elements must be CharSequence.
Common
joining(delim)
Purpose: Concatenate with delimiter.
String s = Stream.of("a","b").collect(Collectors.joining(", "));
Corner case / tip: Common for CSV-like outputs.
Common
joining(delim,prefix,suffix)
Purpose: Concatenate with adornments.
String s = Stream.of("a","b").collect(Collectors.joining(",
","[","]"));
Corner case / tip: Great for pretty printing.
Common
toMap(keyMapper, valueMapper)
Purpose: Collect to Map, error on duplicate key.
Map<String,Integer> m =
items.stream().collect(Collectors.toMap(Item::name, Item::qty));
Corner case / tip: Duplicate keys cause IllegalStateException.
Common
toMap(k, v, merge)
Purpose: Provide merge function for duplicates.
Map<String,Integer> m = items.stream().collect(Collectors.toMap(
Item::name, Item::qty, Integer::max));
Corner case / tip: Common for keeping max/min/last value.
Common
toMap(k, v, merge, mapSupplier)
Purpose: Also choose map type.
TreeMap<String,Integer> m = items.stream().collect(Collectors.toMap(
Item::name, Item::qty, Integer::max, TreeMap::new));
Corner case / tip: Pick LinkedHashMap to preserve order.
Common
toUnmodifiableMap(k, v)
Purpose: Unmodifiable map, error on duplicate key.
Map<String,Integer> m =
items.stream().collect(Collectors.toUnmodifiableMap(Item::name,
Item::qty));
Corner case / tip: Duplicate keys throw IllegalStateException.
toUnmodifiableMap(k, v, merge)
Purpose: Unmodifiable map with merge.
Map<String,Integer> m =
items.stream().collect(Collectors.toUnmodifiableMap(
Item::name, Item::qty, Integer::max));
Corner case / tip: Merge resolves duplicates before freezing.
toConcurrentMap(k, v)
Purpose: Concurrent map, error on duplicate key.
ConcurrentMap<String,Integer> m =
items.parallelStream().collect(Collectors.toConcurrentMap(Item::name,
Item::qty));
Corner case / tip: Use for parallel pipelines.
toConcurrentMap(k, v, merge)
Purpose: Concurrent map with merge.
ConcurrentMap<String,Integer> m =
items.parallelStream().collect(Collectors.toConcurrentMap(
Item::name, Item::qty, Integer::sum));
Corner case / tip: Merge function is required for duplicates.
toConcurrentMap(k, v, merge, mapSupplier)
Purpose: Concurrent map with custom type.
ConcurrentMap<String,Integer> m =
items.parallelStream().collect(Collectors.toConcurrentMap(
Item::name, Item::qty, Integer::sum, ConcurrentHashMap::new));
Corner case / tip: Provide ConcurrentHashMap or subclass.
groupingBy(classifier)
Purpose: Map<K, List<T>> grouping.
Map<String,List<Employee>> byDept =
emps.stream().collect(Collectors.groupingBy(Emp::dept));
Corner case / tip: Default Map is HashMap; values are Lists.
Common
groupingBy(classifier, downstream)
Purpose: Customize value reduction.
Map<String,Long> countByDept =
emps.stream().collect(Collectors.groupingBy(Emp::dept,
Collectors.counting()));
Corner case / tip: Downstream can be any collector.
Common
groupingBy(classifier, mapFactory, downstream)
Purpose: Choose map type and downstream.
TreeMap<String,Double> avgSalary =
emps.stream().collect(Collectors.groupingBy(
Emp::dept, TreeMap::new,
Collectors.averagingDouble(Emp::salary)));
Corner case / tip: Pick LinkedHashMap for stable ordering.
Common
groupingByConcurrent(...)
Purpose: Concurrent variant for parallel pipelines.
ConcurrentMap<String,Long> c =
emps.parallelStream().collect(Collectors.groupingByConcurrent(
Emp::dept, Collectors.counting()));
Corner case / tip: Use with parallel streams for speed.
Common
partitioningBy(predicate)
Purpose: Split into true/false buckets.
Map<Boolean,List<Employee>> part =
emps.stream().collect(Collectors.partitioningBy(Emp::isActive));
Corner case / tip: Always two keys: true/false.
Common
partitioningBy(predicate, downstream)
Purpose: Partition with value reduction.
Map<Boolean,Long> counts =
emps.stream().collect(Collectors.partitioningBy(Emp::isActive,
Collectors.counting()));
Corner case / tip: Useful when predicate is binary.
Common
counting()
Purpose: Number of elements.
long c = stream.collect(Collectors.counting());
Corner case / tip: Equivalent to count().
Common
summingInt/Long/Double(mapper)
Purpose: Sum over mapped values.
int total =
people.stream().collect(Collectors.summingInt(Person::age));
Corner case / tip: Be aware of overflow for int/long sums.
Common
averagingInt/Long/Double(mapper)
Purpose: Average over mapped values.
double avg =
people.stream().collect(Collectors.averagingDouble(Person::salary));
Corner case / tip: Averages are double/Double.
Common
summarizingInt/Long/Double(mapper)
Purpose: Summary stats object.
DoubleSummaryStatistics s =
people.stream().collect(Collectors.summarizingDouble(Person::salary))
;
Corner case / tip: Includes count, sum, min, avg, max.
mapping(mapper, downstream)
Purpose: Map then collect in one pass.
Set<String> names =
people.stream().collect(Collectors.mapping(Person::name,
Collectors.toSet()));
Corner case / tip: Avoids separate map + collect.
filtering(predicate, downstream)
Purpose: Filter then collect in one pass (JDK 9+).
Map<String,List<Employee>> onlyActive =
emps.stream().collect(Collectors.groupingBy(
Emp::dept, Collectors.filtering(Emp::isActive,
Collectors.toList())));
Corner case / tip: Great inside groupingBy.
flatMapping(mapperToStream, downstream)
Purpose: flatMap then collect (JDK 9+).
Map<String,Set<String>> tagSets =
posts.stream().collect(Collectors.groupingBy(
Post::author, Collectors.flatMapping(p -> p.tags().stream(),
Collectors.toSet())));
Corner case / tip: Avoids extra intermediate streams.
reducing(identity, mapper, op)
Purpose: Reduction as collector.
int maxLen = words.stream().collect(Collectors.reducing(0,
String::length, Integer::max));
Corner case / tip: Prefer summarizing/averaging when available.
collectingAndThen(downstream, finisher)
Purpose: Post-process collector result.
List<String> unmod =
words.stream().collect(Collectors.collectingAndThen(
Collectors.toList(), List::copyOf));
Corner case / tip: Use for wrapping (e.g., unmodifiable).
Common
teeing(down1, down2, merger) (JDK 12+)
Purpose: Run two collectors at once then merge.
record MinMax(int min, int max) {}
MinMax mm = nums.stream().collect(Collectors.teeing(
Collectors.minBy(Integer::compareTo),
Collectors.maxBy(Integer::compareTo),
(min,max) -> new MinMax(min.orElseThrow(), max.orElseThrow())
));
Corner case / tip: Great for min+max in one pass.
Common
9) Corner Cases & Gotchas
• Empty min/max/average → Optional.empty / OptionalDouble.empty. Handle with
orElse/elseThrow.
• Collectors.toMap without merge → IllegalStateException on duplicate key.
• Stream.peek does nothing without terminal op; avoid side effects.
• Stream.toList() returns unmodifiable list (JDK 16+).
• Files.lines must be closed; use try-with-resources.
• distinct relies on equals/hashCode; do not mutate keys after insertion.
• Parallel forEach is unordered; use forEachOrdered if you need order (slower).
• groupingBy default map is HashMap; use LinkedHashMap/TreeMap if you need order/sort.
10) Practical Patterns & Recipes
Frequency Map
Map<String, Long> freq =
words.stream()
.map(String::toLowerCase)
.collect(Collectors.groupingBy(w -> w,
Collectors.counting()));
Top N per Group
Map<String, List<Employee>> top3 =
emps.stream().collect(Collectors.groupingBy(
Emp::dept,
Collectors.collectingAndThen(
Collectors.toList(),
list -> list.stream()
.sorted(Comparator.comparing(Employee::score)
.reversed())
.limit(3)
.toList()
)
));
Safe toMap with Merge
Map<String, Integer> best =
entries.stream().collect(Collectors.toMap(
e -> e.key(), e -> e.value(), Integer::max
));
First Non-Empty String
Optional<String> first =
strings.stream().filter(s -> s != null && !
s.isBlank()).findFirst();