Unveiling Java Streams: peek vs. forEach

Java Streams has introduced a more functional approach to handling collections and streamlining data processing tasks. Among the plethora of operations available, peek and forEach often come up in discussions, primarily due to their ability to execute actions on stream elements. Despite their similarities, they serve different purposes and have distinct behaviors. In this blog, we delve into the nuances between peek and forEach, highlighting their uses, differences, and best practices.

The Basics

What is peek?

peek is an intermediate operation in the Stream API that allows you to perform a specified action on each element of the stream as it passes through the pipeline. The operation is mainly intended for debugging purposes, where you want to observe the elements without altering the flow of the stream. Since peek is lazy, the actions specified within it won’t be executed until a terminal operation is invoked.

    Stream<T> peek(Consumer<? super T> action);

Key Characteristics:

  • Intermediate operation.
  • Mainly used for debugging.
  • Does not consume the stream.
  • Actions are executed only upon the invocation of a terminal operation.

What is forEach?

forEach is a terminal operation that is used to iterate over each element of the stream, performing a specified action. It marks the end of the stream processing, consuming the stream entirely. Once forEach is called, the stream cannot be used or referred to for further operations.

 void forEach(Consumer<? super T> action);

Key Characteristics:

  • Terminal operation.
  • Used to perform actions on each element of the stream.
  • Consumes the stream, concluding its processing.
  • Actions are executed immediately.

Understanding the Differences

While both peek and forEach accept a Consumer<T> functional interface, allowing you to specify an action to be performed on each element, their operational nature and use cases differ significantly.

Operation Type

  • peek: Serves as a pass-through gate within the stream pipeline, allowing further operations to be performed on the stream.
  • forEach: Serves as an endpoint for stream processing, concluding the operations on the stream.

Purpose and Use Case

  • peek: Ideal for debugging or logging to observe the elements as they pass through a certain point in the stream.
  • forEach: Used to apply a final action on each element, such as collecting, printing, or storing the elements after all transformations have been applied.

Laziness vs. Eagerness

  • peek: Exhibits laziness, with actions being deferred until necessary.
  • forEach: Exhibits eagerness, with actions being applied immediately.

Practical Examples

To solidify your understanding, let’s look at practical examples of both operations:

Using peek for Debugging

List<String> names = Arrays.asList("John", "Jane", "Doe");
long count =
                  .peek(System.out::println) // Debugging to print elements
                  .count(); // Triggers the stream processing

In this example, peek is used to print each element for debugging purposes before counting the elements in the stream.

Using forEach for Action Execution

List<String> names = Arrays.asList("John", "Jane", "Doe");
     .forEach(System.out::println); // Directly prints each element

Here, forEach is used to perform an action (printing) on each element of the stream, marking the end of stream processing.


Understanding the distinction between peek and forEach is crucial for effective stream processing in Java. peek allows for intermediate inspection without disrupting the stream, making it perfect for debugging. In contrast, forEach is your go-to for executing actions on each element, marking the completion of stream processing. By mastering their differences and uses, you can leverage the Java Stream API more effectively in your development endeavors.



About Author

As Neelabh Singh, I am a Senior Software Engineer with 6.6 years of experience, specializing in Java technologies, Microservices, AWS, Algorithms, and Data Structures. I am also a technology blogger and an active participant in several online coding communities.

You may also like

Blog Design Pattern

Understanding the Builder Design Pattern in Java | Creational Design Patterns | CodeTechSummit

Overview The Builder design pattern is a creational pattern used to construct a complex object step by step. It separates
Blog Tech Toolkit

Base64 Decode

Base64 encoding is a technique used to encode binary data into ASCII characters, making it easier to transmit data over