ZetCode

Java Stream peek

last modified May 24, 2025

This article demonstrates how to use the Java Stream peek method for debugging and inspecting stream elements during processing.

The peek method is an intermediate operation in the Java Stream API that allows you to perform a non-interfering action on each element of a stream as it passes through the pipeline, returning a new stream with the same elements. Primarily designed for debugging, it accepts a Consumer function to observe or log elements without altering them.

For example, you can use peek to print intermediate values or track the state of elements during complex stream operations. While its main purpose is debugging, it can also support non-interfering side effects, such as logging or metrics collection, provided these actions adhere to the stream API's non-interference requirements. However, developers should use peek cautiously, as improper use (e.g., modifying elements or introducing stateful behavior) can violate stream semantics and lead to unpredictable results, especially in parallel streams.

Basic peek Syntax

The peek method in the Java Stream API is defined with a single method signature that enables inspection of stream elements through a provided action. This signature is straightforward, making it easy to integrate into stream pipelines for debugging or non-interfering side effects.


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

The peek method accepts a Consumer, a functional interface that takes a single input argument of type T (or a supertype) and returns no result. This Consumer defines the action to be performed on each stream element as it passes through the pipeline, such as logging or inspecting its state. The peek operation is non-destructive, returning a new stream containing the same elements as the original stream, ensuring the pipeline's data remains unchanged.

While primarily used for debugging (e.g., printing intermediate values), the action must be non-interfering to comply with stream API requirements, meaning it should not modify the stream's source or introduce stateful behavior. Developers should exercise caution, especially in parallel streams, where side effects from peek could lead to unpredictable outcomes if not properly managed.

Debugging stream operations

In the following example, we will use peek to log elements as they pass through the stream pipeline. This is useful for debugging and understanding how data flows through the stream.

Main.java
void main() {

    long count = Stream.of("apple", "banana", "cherry", "date")
        .peek(e -> System.out.println("Original: " + e))
        .filter(s -> s.length() > 4)
        .peek(e -> System.out.println("Filtered: " + e))
        .map(String::toUpperCase)
        .peek(e -> System.out.println("Mapped: " + e))
        .count();
    
    System.out.println("Total count: " + count);
}

This example demonstrates how peek can be used to observe elements at different stages of stream processing. Each peek call logs the current state of elements.

$ java Main.java
Original: apple
Original: banana
Filtered: banana
Mapped: BANANA
Original: cherry
Filtered: cherry
Mapped: CHERRY
Original: date
Total count: 2

Understanding lazy evaluation

peek helps visualize stream's lazy evaluation behavior.

Main.java
void main() {

    Stream.of("one", "two", "three", "four")
        .peek(e -> System.out.println("Before filter: " + e))
        .filter(e -> e.length() > 3)
        .peek(e -> System.out.println("After filter: " + e))
        .findFirst()
        .ifPresent(System.out::println);
}

This example shows how streams process elements one at a time until the terminal operation is satisfied. The findFirst operation stops processing after finding the first matching element.

$ java Main.java
Before filter: one
Before filter: two
Before filter: three
After filter: three
three

Tracking state changes

We can use peek to observe state changes in mutable objects.

Main.java
class Counter {

    int count = 0;
    
    void increment() { 
        count++; 
    }
    
    @Override 
    public String toString() { 
        return "Count: " + count; 
    }
}

void main() {

    List<Counter> counters = new ArrayList<>();
    
    for (int i = 0; i < 5; i++) {
        counters.add(new Counter());
    };
    
    counters.stream()
        .peek(c -> System.out.println("Before: " + c))
        .forEach(Counter::increment);
    
    System.out.println("\nAfter processing:");
    counters.forEach(System.out::println);
}

This example shows how peek can observe state changes in mutable objects. Note that modifying objects in peek is generally discouraged as it can lead to unexpected behavior.

$ java Main.java
Before: Count: 0
Before: Count: 0
Before: Count: 0
Before: Count: 0
Before: Count: 0

After processing:
Count: 1
Count: 1
Count: 1
Count: 1
Count: 1

Logging transformations

Logging intermediate transformations in a complex pipeline.

Main.java
record Product(String name, double price, int stock) {
}

void main() {

    Stream.of(
            new Product("Laptop", 999.99, 5),
            new Product("Phone", 699.99, 10),
            new Product("Tablet", 349.99, 0),
            new Product("Monitor", 249.99, 8)
        )
        .peek(p -> System.out.println("Original: " + p))
        .filter(p -> p.stock() > 0)
        .peek(p -> System.out.println("In stock: " + p))
        .map(p -> new Product(p.name(), p.price() * 0.9, p.stock())) // 10% discount
        .peek(p -> System.out.println("Discounted: " + p))
        .forEach(p -> System.out.println("Final: " + p));
}

This example shows how peek can help understand each transformation step in a more complex stream pipeline involving filtering and mapping.

Source

Java Stream peek documentation

In this article we have explored the Java Stream peek method. While primarily designed for debugging, it can be useful for observing stream processing without modifying the stream contents. Remember that peek should generally not be used for operations that modify state or affect the stream outcome.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Java tutorials.