Java 8 Stream.peek() Example to debug Stream

I have been writing about some important methods from Java SE 8  e.g. map(), flatMap() etc from quite some time and today I'll share my experience about another useful method peek() from class. The peek() method of Stream class can be a very useful to debug and understand streams in Java 8. You can use the peek() method to see the elements as they flow from one step to another e.g. when you use the filter() method for filtering, you can actually see how filtering is working e.g. lazy evaluation as well as which elements are filtered. the peek() method returns a stream consisting of the elements of this stream and performs the action requested by the client. The peek() method expect a Consumer instance to perform a non-interfering action on the elements of this stream, usually printing them using forEach() method.

Btw, the sole reason of using peek() is debugging even the API itself says that peek() is only there for debugging, but it does help to understand the lazy evaluation technique Stream uses to improve performance. You can also see Java SE 8 for Really Impatient by Cay S. Horstmann to learn more about the internal working of Stream in Java 8.

In order to understand peek() method better, let's see some code in action. How about using the filter() and map() in a chained pipeline? This will help you to learn how your Java 8 code involving Stream works.

Consider the following example:

List<String> result = Stream.of("EURO/INR", "USD/AUD", "USD/GBP", "USD/EURO")
        .filter(e -> e.length() > 7)
        .peek(e -> System.out.println("Filtered value: " + e))
        .peek(e -> System.out.println("Mapped value: " + e))

In this example, we have a Stream of String and then we are filtering all Strings whose length is greater than 7 and then we are converting them to lowercase using the map() function. Now, how do you think this program will execute? Many of you will think that after the first filter() execution you will get a Stream containing two elements "EURO/INR" and "USD/EURO" and peek() will print those two elements. Well, that's not the case, since Streams are executed lazily, nothing will happen until the collect() method will execute, which is the terminal method.

This is proved by the following output from running above code into Eclipse IDE or command prompt, it will print following lines:

Filtered value: EURO/INR
Mapped value: euro/inr
Filtered value: USD/EURO
Mapped value: usd/euro

The key point to note here is that values are filtered and mapped one by one, not together. It means the code is executed backwards, when collect() method calls the Collectors.toList() to get the result in a List, it ask map() function which in turn ask the filter() method. Since filter() is lazy it return the first element whose length is greater than 7 and sit back until map() ask again.

You can see that peek() method clearly print the value of stream in the pipeline after each call to filter() method. You can further read Java 8 in Action to learn more about different types operation with Stream e.g. intermediate and terminal operation.

Java 8 Stream.peek() Example

How to use peek() method in Java 8

As I said, the Stream.peek() method is very useful for debugging and understating the stream related code in Java. Here are a couple of more example of using peek() to understand how bulk data operations is processed by Stream utility.

package test;

import java.util.ArrayList;
import java.util.List;

 * Java 8 peek() method example
public class Test {

    public static void main(String[] args) {

        List<String> versions = new ArrayList<>();
        versions.add("Jelly Bean");
        versions.add("Ice Cream Sandwidch");

        // filtering all vaersion which are longer than 7 characters
                .filter(s -> s.length() > 7)
                .peek(e -> System.out.println("After the first filter: " + e))
                .filter(s -> s.startsWith("H"))
                .peek(e -> System.out.println("After the second filter: " + e))



After the first filter: Lollipop
After the first filter: Jelly Bean
After the first filter: Ice Cream Sandwich
After the first filter: Honeycomb
After the second filter: Honeycomb
After the first filter: Gingerbread

By looking at this output, can you explain how the code would have been executed? Well, it seems that when to collect() ask the second filter() method, it ask first filter() and you can see that first Lollipop passed the first filter but couldn't pass the second one because it doesn't start with letter "H".

So, the second filter() again ask first() filter for an element, it return Jelly Bean, Ice Cream Sandwich, and HoneyComb one by one. Since HoneyComb made passed second filter it is collected by Collector and again the same process happens but aborted after GingerBread because all elements in Stream is already processed.

This clearly explains the lazy execution behavior of Stream as opposed to eager iterative implementation and peek() help you to understand this better. See Mastering Lambdas: Java Programming in a Multicore World by Maurice Naftalin  to learn more about lazy evaluation and performance benefit of Stream in Java 8.

Important points

1) The peek() method of Stream class is an intermediate method, hence you can call other stream methods after this.

2) It returns a new Stream, which is basically the stream it got.

3) It accepts an object of functional interface Consumer to perform non-interfering action e.g. printing values.

4) For parallel stream pipelines, the action may be called at whatever time and whatever thread the element is made available by the upstream operation.

That's all about how to use the peek() method in Java 8. You can use the peek() method for debugging. It allows you to see the elements as they flow past a certain point in the pipeline. By using this you can check whether your filter() method is working properly or not. You can see exactly which elements are got filtered by using peek() in Java 8.

Other Java 8 tutorials for Java Programmers



  1. I'm not sure why the inconsistency between the 2 examples?
    In example 1, there is a List (result) and intermediate filtering functions are immediately called on it.
    In example 2, there is an ArrayList (version) and the "stream" function is called before filtering?

    They both implement the Collection interface. Also, I haven't been able to find the Stream.of static method either.

    1. Hello Joe, in first example, there is no list, in fact we have directly created a Stream by using Stream.of() method. In the second example, we have retrieved Stream by calling stream() method on an ArrayList object. The key thing they demonstrate is how filtering works in Stream, you can choose whatever you want to implement stream example. Btw, where did you look for Stream.of() function?