Java Streams, added in Java 8, became popular very fast and are a powerful way to process collections of objects. A Stream is a sequence of objects from a source and supports chained methods to produce the desired result.

Debugging Java streams can be challenging. In this post, we will learn to debug the streams as their elements are processed in the chained method calls.

1. Why are Streams Hard to Debug?

Java 8 Streams may sometimes be difficult to debug. This happens because they require us to insert additional breakpoints and thoroughly analyze each transformation inside the stream.

For example, we have the Student class:

public class Student {

    private String name;
    private String email;
    private Double grade;
    
    //getters, setters, constructor, toString method
}

We can create a list of students:

List<Student> students = List.of(
    new Student("Alexandru","alex@gmail.com",5.6),
    new Student("Emmanuela","emma@yahoo.com",7.2),
    new Student("John","john@gmail.com",10.0),
    new Student("Andrew","andrew",6.2),
    new Student("Anna","anna@gmail.com",6.2)
);

Suppose, we want to get all the students in alphabetical order that have a valid email address and a passing grade. So we use the stream API operations:

List<Student> newList = students.stream()
    .filter(student -> student.getEmail().endsWith("@gmail.com"))
    .filter(student -> student.getGrade() > 5.0)
    .sorted(Comparator.comparing(Student::getName))
    .collect(Collectors.toList());

After running the program we get only one student. So we want to debug the stream to see how it filters the students.

See Also: Guide to Java Stream API

2. Debug using peek() API

We can debug the stream using the peek() method to log the information about the data at every step. The peek() method returns a stream consisting of the elements of the source stream and performs the action requested by the client of each element.

List<Student> newList = students.stream()
    .filter(student -> student.getEmail().endsWith("@gmail.com"))
    .peek(student -> System.out.println("Filtered 1 value:" + student))
    .filter(student -> student.getGrade() > 5.0)
    .peek(student -> System.out.println("Filtered 2 value:" + student))
    .sorted(Comparator.comparing(Student::getName))
    .collect(Collectors.toList());

Notice the program output. We can see that peek() method clearly prints the elements of the stream in the pipeline after each call to filter() method. We can see that 3 students pass the first filter and only one passes the second one too.

Filtered 1 value:Student{name="Alexandru", email="alex@gmail.com", grade=2.6}
Filtered 1 value:Student{name="John", email="john@gmail.com", grade=10.0}
Filtered 2 value:Student{name="John", email="john@gmail.com", grade=10.0}
Filtered 1 value:Student{name="Anna", email="anna@gmail.com", grade=4.2}

3. Debug using IntelliJ Stream Debugger

The IntelliJ Stream Debugger is a hidden gem and is very easy to use. It allows you to visualize the stream. Let’s use this in our example.

For the first step, we will set a breakpoint on the stream.

Now we will run the program in debugging mode. The program will be suspended when the stream is created.


And now we will press the button “Trace Current Stream Chain”. A new tab will open, and here we can see how the stream does filter the data.

4. Conclusion

Streams may be seen as difficult to debug. But here the special methods of the Streams API, as well as the special tools of the IDEs that we use daily, come to our aid.

Happy Learning !!

Was this post helpful?

Join 8000+ Awesome Developers, Like YOU!

Leave a Comment

About HowToDoInJava

This blog provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions, and frequently asked interview questions.