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

📍 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

