Java 8 Stream API — A Complete Guide (Part-2)

🔥 Java 8 Stream API — A Complete Guide (Part-2)

Advanced Java 8 Stream API — groupingBy, partitioningBy, flatMap, Custom Collectors & More

Stream API Advanced

📍 Part-1: Java 8 Stream API — A Complete Guide

In Part-1, we explored the fundamentals of Java 8 Stream API like map, filter, and reduce.

In this part, we’ll dive deeper into powerful operations that help solve real-world problems, including:

  • Grouping and partitioning collections
  • Flattening nested data
  • Creating custom collectors
  • Multi-level aggregations

Collectors.groupingBy()

  • A classifier function that maps an element to a key.
  • (Optional) Downstream collector (e.g., counting, summing, mapping, etc.)
  • It returns a Map<K, List<T>> (or another Map<K, R> if using downstream collectors).
Collectors.groupingBy(Function<? super T, ? extends K>)
Collectors.groupingBy(Function, Collector)

✅ Problem Statement:

You need to group employees by their department to populate a department-wise team view.

public class Employee {
String name;
String department;

Employee(String name, String department) {
this.name = name;
this.department = department;
}
public String getDepartment() { return department; }
public String getName() { return name; }
}

List<Employee> employees = Arrays.asList(
new Employee("Alice", "HR"),
new Employee("Bob", "Engineering"),
new Employee("Charlie", "HR"),
new Employee("David", "Engineering")
);

Map<String, List<Employee>> groupedByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
groupedByDept.forEach((dept, empList) -> {
String names = empList.stream()
.map(Employee::getName)
.collect(Collectors.joining(", "));
System.out.println(dept + ": " + names);
});

✅ Output:

HR: Alice, Charlie  
Engineering: Bob, David

Collectors.partitioningBy()

  • A Predicate to test each element (returns true/false).
  • A Map<Boolean, List<T>> where:
  • true → elements matching the predicate
  • false → the rest
Collectors.partitioningBy(Predicate<? super T>)

✅ Problem Statement:

You need to separate even and odd numbers from a list of integers.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

Map<Boolean, List<Integer>> partitioned = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));

System.out.println("Even numbers: " + partitioned.get(true));
System.out.println("Odd numbers: " + partitioned.get(false));

✅ Output:

Even numbers: [2, 4, 6]  
Odd numbers: [1, 3, 5]

flatMap() — Flattening Nested Structures

  • A function that returns a Stream for each element.
  • A flattened stream of all sub-elements.
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)

✅ Problem Statement:

You have a nested list of programming languages, and you want a flat list of all languages.

List<List<String>> nestedList = Arrays.asList(
Arrays.asList("Java", "Python"),
Arrays.asList("C++", "Go"),
Arrays.asList("Rust")
);

List<String> flatList = nestedList.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println(flatList);

✅ Output:

[Java, Python, C++, Go, Rust]

Custom Collectors

Collector.of(Supplier, Accumulator, Combiner, Finisher)

What it takes:

  • Supplier → provides new container
  • Accumulator → adds elements to container
  • Combiner → merges containers (parallel streams)
  • Finisher → final transformation
  • A custom aggregation result — e.g., formatted string, calculated value, etc.

✅ Problem Statement:

You want to join a list of strings in uppercase, separated by ” | “.

List<String> words = Arrays.asList("java", "stream", "collector");

Collector<String, StringJoiner, String> customCollector = Collector.of(
() -> new StringJoiner(" | "),
(joiner, word) -> joiner.add(word.toUpperCase()),
StringJoiner::merge,
StringJoiner::toString
);
String result = words.stream().collect(customCollector);
System.out.println(result);

✅ Output:

JAVA | STREAM | COLLECTOR

Real-World Example: Group & Count

Method: groupingBy + counting

Count how many elements are grouped under each key.

Collectors.groupingBy(..., Collectors.counting())

Returns:

A Map<K, Long>

✅ Problem Statement:

You want to count how many books each author has written.

class Book {
String title;
String author;

Book(String title, String author) {
this.title = title;
this.author = author;
}
public String getAuthor() { return author; }
}
List<Book> books = Arrays.asList(
new Book("Book A", "Author 1"),
new Book("Book B", "Author 2"),
new Book("Book C", "Author 1"),
new Book("Book D", "Author 3"),
new Book("Book E", "Author 2")
);
Map<String, Long> bookCountByAuthor = books.stream()
.collect(Collectors.groupingBy(Book::getAuthor, Collectors.counting()));
bookCountByAuthor.forEach((author, count) ->
System.out.println(author + ": " + count));

✅ Output:

Author 1: 2  
Author 2: 2
Author 3: 1

Nested Grouping

Collectors.groupingBy(Classifier1, groupingBy(Classifier2))

What it does:

Groups elements in multiple levels, like by department → then by designation → then by list of employees.

✅ Problem Statement

You need to generate multi-level reports, like employees grouped by department and then by designation.

class Employee {
String name;
String department;
String designation;

Employee(String name, String department, String designation) {
this.name = name;
this.department = department;
this.designation = designation;
}
public String getDepartment() { return department; }
public String getDesignation() { return designation; }
public String getName() { return name; }
}
List<Employee> employees = Arrays.asList(
new Employee("Alice", "HR", "Manager"),
new Employee("Bob", "Engineering", "Developer"),
new Employee("Charlie", "HR", "Recruiter"),
new Employee("David", "Engineering", "Developer"),
new Employee("Eva", "Engineering", "Manager")
);
Map<String, Map<String, List<Employee>>> multiLevelGroup = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment,
Collectors.groupingBy(Employee::getDesignation)));
multiLevelGroup.forEach((dept, desMap) -> {
System.out.println("Department: " + dept);
desMap.forEach((designation, empList) -> {
String names = empList.stream()
.map(Employee::getName)
.collect(Collectors.joining(", "));
System.out.println(" " + designation + ": " + names);
});
});

✅ Output:

Department: HR  
Manager: Alice
Recruiter: Charlie
Department: Engineering
Developer: Bob, David
Manager: Eva

⚠️ Tips for Using Advanced Stream API

  • ✅ flatMap() is powerful for flattening but can make code harder to read — use wisely.
  • ✅ groupingBy() and partitioningBy() are great for aggregation — but watch out for memory usage in large datasets.
  • ✅ Use Collectors.toMap() when you want to directly create a Map<K, V>.
  • ✅ Prefer custom collectors when built-in ones don’t solve the problem exactly.

✅ Conclusion

Java 8’s Stream API is a true game-changer — with its advanced operations, you can handle complex grouping, data flattening, partitioning, and custom aggregations, all in a clean, declarative, and readable way. Whether you’re processing logs, generating reports, or working with nested data, mastering these techniques will help you write modern, efficient, and maintainable Java code.

Happy coding! 💻✨


🔥 Java 8 Stream API — A Complete Guide (Part-2) was originally published in Javarevisited on Medium, where people are continuing the conversation by highlighting and responding to this story.

This post first appeared on Read More