allMatch, noneMatch, findFirst, flatMap, groupingBy, peek, limit, skip, collecting to a Set and summary statistics — solutions, pitfalls and follow-ups.
Published September 25, 2026
These questions cover matching, flattening, grouping and statistics, which are the operations that come up when streams meet real data. The sample data:
List<Integer> numbers = List.of(4, 9, 1, 12, 7, 4, 6);
record User(String name, int age, String city) { }
List<User> users = List.of(new User("Asha", 28, "Pune"), new User("Ravi", 34, "Delhi"),
new User("Meera", 28, "Delhi"), new User("Kabir", 41, "Pune"));
boolean allPositive = numbers.stream().allMatch(n -> n > 0); // true
Explain: allMatch short-circuits at the first element that fails.
Key points to cover:
allMatch on an empty stream returns true ("vacuous truth"). If an empty list should count as a failure, check isEmpty() first.Learn it in depth → Streams API
boolean noNegatives = numbers.stream().noneMatch(n -> n < 0); // true
Key points to cover:
noneMatch(p) is equivalent to !anyMatch(p).true for an empty stream.anyMatch returns false for an empty stream.Optional<Integer> first = numbers.stream().findFirst(); // Optional[4]
Optional<User> firstAdult = users.stream().filter(u -> u.age() >= 30).findFirst(); // Ravi
Explain: findFirst respects encounter order, and short-circuits.
Key points to cover:
findFirst throws a NullPointerException if the first element is null, because an Optional can't hold null. Filter out nulls first when your data may contain them.List<List<Integer>> nested = List.of(List.of(1, 2), List.of(3, 4, 5), List.of());
List<Integer> flat = nested.stream().flatMap(List::stream).toList(); // [1, 2, 3, 4, 5]
Explain: flatMap maps each inner list to a stream, and concatenates the streams into one.
Key points to cover:
orders.stream().flatMap(o -> o.lines().stream()).mapMulti is an alternative that avoids creating a stream per element.Map<Integer, List<User>> byAge = users.stream()
.collect(Collectors.groupingBy(User::age));
// {28=[Asha, Meera], 34=[Ravi], 41=[Kabir]} (HashMap: no guaranteed order)
Map<String, Long> countByCity = users.stream()
.collect(Collectors.groupingBy(User::city, TreeMap::new, Collectors.counting()));
// {Delhi=2, Pune=2}: sorted keys, because we asked for a TreeMap
Map<String, List<String>> namesByCity = users.stream()
.collect(Collectors.groupingBy(User::city, Collectors.mapping(User::name, Collectors.toList())));
Explain: groupingBy(classifier) builds a map from each key to the list of matching elements. The second and third arguments let you choose the map type and a downstream collector (counting, mapping, summingInt, averagingDouble, maxBy).
Key points to cover:
partitioningBy(predicate) is the special case with two groups: Map<Boolean, List<T>>.Learn it in depth → Collectors
List<Integer> result = numbers.stream()
.filter(n -> n > 5)
.peek(n -> log.debug("passed filter: {}", n))
.map(n -> n * 10)
.toList();
Explain: peek runs an action on each element as it flows past, and passes the element on unchanged. It's meant for debugging.
Key points to cover:
peek is lazy. Without a terminal operation nothing is printed.count() on a sized stream.peek for business side effects, such as saving to a database or modifying state. Use forEach, or a proper loop.List<Integer> firstThree = numbers.stream().limit(3).toList(); // [4, 9, 1]
Key points to cover:
limit is short-circuiting, so it even works on infinite streams: Stream.iterate(1, n -> n * 2).limit(10).limit can be expensive, because it must preserve encounter order.skip(page * size).limit(size).List<Integer> rest = numbers.stream().skip(2).toList(); // [1, 12, 7, 4, 6]
Key points to cover:
dropWhile and takeWhile (Java 9) skip or take elements while a condition holds. That's useful on sorted data.Set to remove duplicates.Set<Integer> unique = numbers.stream().collect(Collectors.toSet()); // no order guarantee
Set<Integer> ordered = numbers.stream().collect(Collectors.toCollection(LinkedHashSet::new));
Set<Integer> sorted = new TreeSet<>(numbers); // no stream needed
Key points to cover:
Collectors.toSet() currently gives a HashSet, but that's not guaranteed. Use toCollection(...) for a specific type, or Collectors.toUnmodifiableSet() for an immutable one.new HashSet<>(list) or Set.copyOf(list) is simpler than a stream.IntSummaryStatistics stats = numbers.stream().mapToInt(Integer::intValue).summaryStatistics();
stats.getMin(); // 1
stats.getMax(); // 12
stats.getAverage(); // 6.142857…
stats.getSum(); // 43 (a long, so no int overflow)
stats.getCount(); // 7
DoubleSummaryStatistics ageStats = users.stream().collect(Collectors.summarizingDouble(User::age));
Explain: summaryStatistics computes all five values in a single pass, which is more efficient than five separate streams.
Key points to cover:
min is Integer.MAX_VALUE, max is Integer.MIN_VALUE, average is 0, and count is 0. Check getCount() before trusting the values.Q: How do you count word frequencies with streams?
A: Arrays.stream(text.toLowerCase().split("\\W+")).filter(w -> !w.isBlank()).collect(Collectors.groupingBy(Function.identity(), Collectors.counting())).
Q: How do you convert a list to a map, and what happens with duplicate keys?
A: list.stream().collect(Collectors.toMap(User::name, User::age)). It throws an IllegalStateException on a duplicate key unless you pass a merge function: toMap(k, v, (a, b) -> a). It also throws an NPE for null values.
Q: What's the difference between groupingBy and partitioningBy?
A: partitioningBy always produces exactly two keys (true and false), even if one group is empty. groupingBy creates a key only for the values that actually occur.
Q: Is forEach guaranteed to process elements in order?
A: Not on parallel streams. Use forEachOrdered when order matters, at some cost to parallelism.