DEV Community

Priyank Bhardwaj
Priyank Bhardwaj

Posted on

Advanced Stream Techniques in Java 8

Java 8 introduced Streams API, which changed the way developers process collections. Instead of writing long loops and temporary variables, streams allow us to write declarative, functional-style code.

Most developers are comfortable with basic operations like filter, map, and collect. However, Java Streams offer many advanced techniques that can make code more expressive, efficient, and readable.

In this article, we will explore some advanced stream techniques every Java developer should know.


Understanding Lazy Evaluation

One of the most powerful characteristics of streams is lazy evaluation.

Intermediate operations like filter, map, and sorted are not executed until a terminal operation is called.

List<String> names = Arrays.asList("John", "Jane", "Jack", "Doe");

names.stream()
     .filter(name -> {
         System.out.println("Filtering: " + name);
         return name.startsWith("J");
     })
     .map(name -> {
         System.out.println("Mapping: " + name);
         return name.toUpperCase();
     })
     .findFirst();
Enter fullscreen mode Exit fullscreen mode

Output

Filtering: John
Mapping: John
Enter fullscreen mode Exit fullscreen mode

Even though there are multiple elements, only the first match is processed due to short-circuiting.

Why this matters

Lazy evaluation improves performance because the stream processes only what is necessary.


Using flatMap for Nested Collections

When working with nested collections, flatMap is extremely useful.

Example Problem

Suppose we have a list of students and each student has multiple courses.

class Student {
    String name;
    List<String> courses;
}
Enter fullscreen mode Exit fullscreen mode

We want a single list of all courses.

List<Student> students = getStudents();

List<String> courses =
    students.stream()
            .flatMap(student -> student.getCourses().stream())
            .collect(Collectors.toList());
Enter fullscreen mode Exit fullscreen mode

What flatMap does

Instead of producing:

Stream<List<String>>
Enter fullscreen mode Exit fullscreen mode

It flattens the structure into:

Stream<String>
Enter fullscreen mode Exit fullscreen mode

This is extremely powerful when working with nested data structures.


Parallel Streams

Streams can be parallelized easily using parallelStream().

List<Integer> list = Arrays.asList(10, 20, 60, 30, 80, 90);

list.parallelStream()
    .filter(n -> n > 50)
    .map(n -> n * 2)
    .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

Output

160
120
180
Enter fullscreen mode Exit fullscreen mode

Parallel streams use the ForkJoinPool internally.

When to use parallel streams

Good for:

  • CPU intensive tasks
  • Large datasets
  • Independent operations

Avoid when:

  • Operations involve shared mutable state
  • Tasks are very small
  • Order matters heavily

Avoiding Common Stream Mistakes

1 Modifying external variables

Bad practice:

List<Integer> result = new ArrayList<>();

list.stream().forEach(n -> result.add(n * 2));
Enter fullscreen mode Exit fullscreen mode

Better

List<Integer> result =
    list.stream()
        .map(n -> n * 2)
        .collect(Collectors.toList());
Enter fullscreen mode Exit fullscreen mode

2 Overusing Streams

Streams are powerful, but sometimes simple loops are more readable.

Always prioritize clarity over cleverness.


Combining Streams

You can concatenate streams easily.

Stream<Integer> stream1 = Stream.of(1,2,3);
Stream<Integer> stream2 = Stream.of(4,5,6);

Stream<Integer> combined = Stream.concat(stream1, stream2);

combined.forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

Output:

1 2 3 4 5 6
Enter fullscreen mode Exit fullscreen mode

Conclusion

Java 8 Streams introduced a powerful functional programming model to Java. While basic operations are widely used, mastering advanced techniques can greatly improve your code quality and performance.

Key takeaways:

  • Streams use lazy evaluation
  • flatMap helps flatten nested collections
  • Advanced collectors enable powerful data transformations
  • Parallel streams can improve performance for large workloads
  • Custom collectors provide flexibility for specialized tasks
  • When used correctly, streams allow you to write clean, concise, and expressive code.

What's next?

In the next part we will see the best practices and some short comings of java streams.

Top comments (0)