📘 Premium Read: Access my best content on Medium member-only articles — deep dives into Java, Spring Boot, Microservices, backend architecture, interview preparation, career advice, and industry-standard best practices.
✅ Some premium posts are free to read — no account needed. Follow me on Medium to stay updated and support my writing.
🎓 Top 10 Udemy Courses (Huge Discount): Explore My Udemy Courses — Learn through real-time, project-based development.
▶️ Subscribe to My YouTube Channel (172K+ subscribers): Java Guides on YouTube
When working with Java Streams, especially with Collectors.groupingBy()
, it's common to end up with nested Map<K, Map<K2, V>>
structures. While these are useful for grouping, they can quickly become unwieldy when you need to flatten the data for reporting, filtering, or transformation.
In this article, you'll learn:
- 🧱 How nested maps are created using
groupingBy()
- 🔁 How to flatten them using Streams
- 📦 Real-world examples with product and sales data
- 🧠 Best practices for working with flattened results
🧪 Sample Domain
Let’s define a simple domain for our examples:
record Product(String name, String category, String region, double sales) {}
List<Product> products = List.of(
new Product("iPhone", "Electronics", "Asia", 50000),
new Product("TV", "Electronics", "Asia", 40000),
new Product("Laptop", "Electronics", "US", 120000),
new Product("Shirt", "Clothing", "US", 2500),
new Product("Jeans", "Clothing", "Asia", 3000)
);
🧱 1. Grouping Products by Category and Region
Let’s say you want to group products by category and then by region:
Map<String, Map<String, List<Product>>> nestedMap = products.stream()
.collect(Collectors.groupingBy(
Product::category,
Collectors.groupingBy(Product::region)
));
This produces a structure like:
{
Electronics = {
Asia = [iPhone, TV],
US = [Laptop]
},
Clothing = {
Asia = [Jeans],
US = [Shirt]
}
}
✅ 2. Flattening Nested Maps into Flat List of Entries
🎯 Goal: Convert to a flat structure like:
List<String> => "Category: Electronics, Region: Asia, Products: 2"
💡 Solution:
List<String> flatList = nestedMap.entrySet().stream()
.flatMap(categoryEntry ->
categoryEntry.getValue().entrySet().stream()
.map(regionEntry -> {
String category = categoryEntry.getKey();
String region = regionEntry.getKey();
int count = regionEntry.getValue().size();
return "Category: " + category + ", Region: " + region + ", Products: " + count;
})
)
.toList();
flatList.forEach(System.out::println);
🧾 Output:
Category: Electronics, Region: Asia, Products: 2
Category: Electronics, Region: US, Products: 1
Category: Clothing, Region: Asia, Products: 1
Category: Clothing, Region: US, Products: 1
✅ 3. Flatten and Aggregate — Total Sales per Category-Region
What if you want to calculate total sales instead of just flattening?
List<String> flatSales = nestedMap.entrySet().stream()
.flatMap(categoryEntry ->
categoryEntry.getValue().entrySet().stream()
.map(regionEntry -> {
String category = categoryEntry.getKey();
String region = regionEntry.getKey();
double totalSales = regionEntry.getValue().stream()
.mapToDouble(Product::sales)
.sum();
return "Category: " + category + ", Region: " + region + ", Sales: " + totalSales;
})
)
.toList();
flatSales.forEach(System.out::println);
🧾 Output:
Category: Electronics, Region: Asia, Sales: 90000.0
Category: Electronics, Region: US, Sales: 120000.0
Category: Clothing, Region: Asia, Sales: 3000.0
Category: Clothing, Region: US, Sales: 2500.0
✅ 4. Returning as Map<String, Double>
(Flat Map Key)
You might prefer returning a map with composite keys like "Electronics-Asia"
:
Map<String, Double> salesByCategoryRegion = nestedMap.entrySet().stream()
.flatMap(categoryEntry ->
categoryEntry.getValue().entrySet().stream()
.map(regionEntry -> {
String key = categoryEntry.getKey() + "-" + regionEntry.getKey();
double totalSales = regionEntry.getValue().stream()
.mapToDouble(Product::sales)
.sum();
return Map.entry(key, totalSales);
})
)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
System.out.println(salesByCategoryRegion);
🧾 Output:
{Electronics-Asia=90000.0, Electronics-US=120000.0, Clothing-Asia=3000.0, Clothing-US=2500.0}
🧠 Best Practices for Flattening Nested Maps
Practice | Benefit |
---|---|
Use flatMap() |
Unroll nested structures easily |
Use Map.Entry |
Clean key-value pair creation |
Combine with Collectors.toMap() |
Return flat maps |
Stream internal values | Aggregate lists in-place |
📌 Summary
Use Case | Stream Pattern |
---|---|
Flatten nested map | .flatMap(map -> innerMap.stream()) |
Return a flat list | Use toList() |
Return a flat map | Use Collectors.toMap() |
Aggregate values | Use mapToX().sum() or downstream collectors |
✅ Final Thoughts
Flattening nested map results with Java Streams allows you to:
- Extract clean reports
- Reshape complex aggregations
- Make downstream processing easier
Think of
flatMap()
as a "flatten + map" combo — and pair it with collectors to produce readable, powerful stream transformations.
Comments
Post a Comment
Leave Comment