Definition
- SIZED stream from
Arrays.asList(1,2,3)has a known size of 3. - count() optimization (Java 9+): returns the known size without traversing.
- peek is upstream: since no elements are pulled,
printlnnever runs. - Adding a filter forces traversal because the size becomes unknown.
- peek is for debugging only: the runtime may legally skip it.
Priya at National Bank of India only wants a head count. The belt operator reads the counter on the side of the belt and never lets the forms pass her desk, so she writes nothing in her notebook.
Understanding the Concepts
What is the output?
In Java 9 and later, the program prints nothing and count() returns 3.
Arrays.asList(1, 2, 3) is a SIZED source with a known size. In Java 9+, count() is optimized: if it can determine the size without executing the pipeline (because no size-changing operation like filter is present), it returns the size directly. Since peek is upstream and elements are never pulled through, its println never runs.
How do you demonstrate it?
package com.bank.core;
import java.util.Arrays;
public class PeekCountDemo {
public static void main(String[] args) {
System.out.println("--- Without filter (Java 9+) ---");
long count1 = Arrays.asList(1, 2, 3).stream()
.peek(System.out::println)
.count();
System.out.println("Count: " + count1);
System.out.println("\n--- With filter (forces traversal) ---");
long count2 = Arrays.asList(1, 2, 3).stream()
.filter(n -> n > 0)
.peek(System.out::println)
.count();
System.out.println("Count: " + count2);
}
}
--- Without filter (Java 9+) ---
Count: 3
--- With filter (forces traversal) ---
1
2
3
Count: 3
Without filter: nothing printed. With filter: all three printed because count must traverse to apply the filter.
How did this change between Java 8 and Java 9?
| Version | Behavior | peek runs? |
|---|---|---|
| Java 8 | count() always traverses | Yes: prints 1, 2, 3 |
| Java 9+ | count() skips traversal for SIZED streams | No: prints nothing |
Why does adding a filter change the behavior?
A filter is a size-changing operation: it might drop elements, so count() can no longer determine the size without traversing. When count must traverse, elements flow through the pipeline, and peek runs.
What are common mistakes?
Mistake: Using peek for business logic like audit logging.
// WRONG: peek for audit logging (may be skipped)
staff.stream()
.peek(e -> auditService.log(e)) // may not execute!
.count();
// CORRECT: use forEach for actions
staff.stream().forEach(e -> auditService.log(e));
long count = staff.stream().count();
What about flatMap and count?
Adding a flatMap (size-changing) also forces traversal, similar to filter.
What about findFirst and peek?
findFirst() is short-circuiting: it pulls only one element, so peek runs for only that one element.
Arrays.asList(1, 2, 3).stream()
.peek(System.out::println)
.findFirst();
// Prints: 1 (only the first element is pulled)
What should a senior engineer know?
This is precisely why peek must never carry side effects that matter. The runtime is legally allowed to skip it. peek is for temporary debugging only. For actions, use forEach. For data, use collect.
What are common interview follow-up questions?
- "What does it print in Java 8?" 1, 2, 3 (count traversed in Java 8).
- "What if you add a filter?" peek runs because count must traverse.
- "Is peek ever safe for business logic?" No: the runtime may skip it.
Visual Overview
Java 9+ without filter: ┌───────────────────────────────────┐ │ Source: [1, 2, 3] SIZED = 3 │ │ peek: upstream, never reached │ │ count(): returns 3 directly │ │ Output: (nothing) │ └───────────────────────────────────┘ Java 9+ with filter: ┌───────────────────────────────────┐ │ Source: [1, 2, 3] SIZED = 3 │ │ filter: size now unknown │ │ count must traverse: │ │ 1 → filter(true) → peek → ✓ │ │ 2 → filter(true) → peek → ✓ │ │ 3 → filter(true) → peek → ✓ │ │ Output: 1, 2, 3 │ └───────────────────────────────────┘
Key Takeaway
In Java 9+, Arrays.asList(1,2,3).stream().peek(println).count() prints nothing and returns 3. The source is SIZED with a known size, and count() is optimized to return that size without traversing when no size-changing operation is present. Since peek is upstream and elements are never pulled, its println never runs.
At the National Bank of India, a peek-based processing log went silent after a Java 9 upgrade for exactly this reason. Adding a filter forces count to traverse because the size becomes unknown, which causes peek to execute.
This is precisely why peek must never carry business-logic side effects: the runtime is legally allowed to skip it. Use forEach for actions and collect for data.
Remember one sentence: count() on a SIZED stream in Java 9+ skips peek entirely because it returns the known size without traversing.
---