Flatten a Stream of Maps to a Single Map in Java
1. Overview
Working with streams of data has become a common task in Java development since Java 8. Often, these streams contain complex structures like maps, which can pose a challenge when processing them further. In this tutorial, I’ll demonstrate how to flatten a stream of maps into a single map with Stream.flatMap()
and Collectors.toMap()
methods.
2. Introduction to the Problem
There are two basic needs that require flattening a list of maps into a single map.
- To get aggregated statistics data. Applications need to process data from multiple sources and collect statistics in the form of maps, such as counts or averages. In order to aggregate the statistics based on a single summary map, we have to flatten the stream of maps first.
- To combine a list of maps into a single map. See the following table for common use cases.
Use Case | Use Case Description |
Configuration Properties | Applications may have different sources of configuration properties, each represented as a map. Admin users need a combined view to ensure that no duplicate properties are configured. |
Database Query Results | Applications may store similar business data into several different databases, so developers need to combine multiple queries’ results into a single map via flattening the stream of query results. |
Merging JSON Documents | Applications need to convert a stream of JSON documents containing key-value pairs into a single map object. |
Configuration Properties Overrides | Systems may have multiple configuration sources: a global base configuration and additional overrides provided by different components. Flattening the stream of configuration maps with a merge function to set the final configuration property. |
Combining Data from Multiple APIs | Applications need to convert API returned data in the form of maps representing different attributes or properties into a single map. |
3. Using Stream.flatMap() and Collectors.toMap()
The Stream interface’s flatMap method is an intermediate operation. It returns a stream consisting of the results of replacing each element of this stream by applying the provided mapping function.
Here is the Stream.flatmap
method specification:
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapperFunction);
The Collectors class’s toMap method returns a Collector
that accumulates elements into a Map
object whose keys and values are the result of applying the provided mapping functions to the input elements.
Here are the Collectors.toMap
specifications:
public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) { return new CollectorImpl<>(HashMap::new, uniqKeysMapAccumulator(keyMapper, valueMapper), uniqKeysMapMerger(), CH_ID); } public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction) { return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new); }
4. Maven Project
In this step, I will create a simple Maven java project which demonstrates how to flatten a list of maps into a single map object.
4.1 Pom.xml
Here is a pom.xml
with the Junit
dependency.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.zheng.demo</groupId> <artifactId>stream-map</artifactId> <version>0.0.1-SNAPSHOT</version> <name>stream-map</name> <description>stream map</description> <dependencies> <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.10.2</version> <scope>test</scope> </dependency> </dependencies> </project>
4.2 Flatten a List of String Map
In this step, I will create a DemoFlatStringMaps
class which has two methods to flatten a List<Map<String, String>>
into a Map<String, String>
object:
with_flatMap
– utilizeStream.flatMap
andCollectors.toMap
. This method will throw aNullPointerException
if themapEntry
‘s value isnull
. It also throws anIllegalStateException
if there are duplicate keys.with_flatMap_andMerge
– utilizeStream.flatMap
andCollectors.toMap
. This method will throw aNullPointerException
if themapEntry
‘s value isnull
.
DemoFlatStringMaps.java
package org.zheng.demo; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.BinaryOperator; import java.util.stream.Collectors; public class DemoFlatStringMaps { private String mergeValue(final String existingValue, final String newValue) { if (!existingValue.equalsIgnoreCase(newValue)) { System.out.println("old=" + existingValue + ", newValue=" + newValue); } // TODO, apply business logic apply to select the right value. return existingValue; } public Map<String, String> with_flatMap(final List<Map<String, String>> listMaps) { Map<String, String> flattenedMap = listMaps.stream().flatMap(map -> map.entrySet().stream()) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); return flattenedMap; } public Map<String, String> with_flatMap_andMerge(final List<Map<String, String>> listMaps) { BinaryOperator<String> mergeFunction = (existingValue, newValue) -> mergeValue(existingValue, newValue); Map<String, String> flattenedMap = listMaps.stream().flatMap(map -> map.entrySet().stream()) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, mergeFunction)); return flattenedMap; } public Map<String, String> with_putAll(final List<Map<String, String>> listMaps) { Map<String, String> flattened = new HashMap<>(); listMaps.stream().forEach(map -> { Map<String, String> combined = map.entrySet().stream() .collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue())); flattened.putAll(combined); }); return flattened; } }
- line 12: A logic to handle the duplicate keys.
- line 16: Select the
existingvalue
when duplcate data is found. Adjust logic based on the application needs. - line 20: Call
flatMap
method to flatten a list of maps to a single map object. - line 21: Call
Collectors.toMap
with method reference syntax. - line 27: Create a
mergeFunction
to select the value when duplicate keys are encountered. - line 30: Use the
mergeFunction
when callingCollectors.toMap
method, this will handle the duplcate keys map data.
4.3 Flatten a List of Object Map
In this step, I will create a DemoFlatObjectMaps
class which has four methods to flatten a List<Map<String, Object>>
into a Map<String, Object>
object:
with_flatMap
– this method is the same as step 4.2 except usingObject
data type. It also utilizesStream.flatMap
andCollectors.toMap
. This method will throw a NullPointerExceptionif themapEntry
‘s value isnull
. It also throws anIllegalStateException
if there are duplicate keys.with_flatMap_optional
– this method usesOptional
to handle thenull
value object.with_flatMap_andMerge_optional
– this method usesOptional
to handlenull
values and amergeFunction
to handle the duplicate keys.with_flatMap_andMerge_optional_l
– this method is the same aswith_flatMap_andMerge_optional
. but using a lambda expression instead ofmergeFunction
.
Both with_flatMap_andMerge_optional
and with_flatMap_andMerge_optional_l
are the robustest methods when flattening a list of maps.
DemoFlatObjectMaps.java
package org.zheng.demo; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.BinaryOperator; import java.util.stream.Collectors; public class DemoFlatObjectMaps { private static Object mergeValue(final Object existingValue, final Object newValue) { if (!existingValue.equals(newValue)) { System.out.println("old=" + existingValue + ", newValue=" + newValue); } return existingValue; } public Map<String, Object> with_flatMap_andMerge_optional(final List<Map<String, Object>> listMaps) { BinaryOperator<Object> mergeFunction = (existingValue, newValue) -> mergeValue(existingValue, newValue); Map<String, Object> flattenedMap = listMaps.stream().flatMap(map -> map.entrySet().stream()).collect(Collectors .toMap(entry -> entry.getKey(), entry -> Optional.ofNullable(entry.getValue()), mergeFunction)); return flattenedMap; } public Map<String, Object> with_flatMap_andMerge_optional_l(final List<Map<String, Object>> listMaps) { Map<String, Object> flattenedMap = listMaps.stream().flatMap(map -> map.entrySet().stream()) .collect(Collectors.toMap(entry -> entry.getKey(), entry -> Optional.ofNullable(entry.getValue()), (existingValue, newValue) -> mergeValue(existingValue, newValue))); return flattenedMap; } public Map<String, Object> with_flatMap_optional(final List<Map<String, Object>> listMaps) { Map<String, Object> flattenedMap = listMaps.stream().flatMap(map -> map.entrySet().stream()) .collect(Collectors.toMap(entry -> entry.getKey(), entry -> Optional.ofNullable(entry.getValue()))); return flattenedMap; } public Map<String, Object> with_flatMap(final List<Map<String, Object>> listMaps) { Map<String, Object> flattenedMap = listMaps.stream().flatMap(map -> map.entrySet().stream()) .collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue())); return flattenedMap; } public Map<String, Object> with_putAll(final List<Map<String, Object>> listMaps) { Map<String, Object> flattedMap = new HashMap<>(); listMaps.stream().forEach(map -> { Map<String, Object> combined = map.entrySet().stream() .collect(Collectors.toMap(entry -> entry.getKey(), entry -> Optional.ofNullable(entry.getValue()))); flattedMap.putAll(combined); }); return flattedMap; } }
- line 21: Create a
mergeFunction
to set the desired value when duplicate keys are encountered. - line 24: Use the
mergeFunction
when callingCollectors.toMap
method. - line 33: It is the same as line 24 except with the lambda expression.
- line 41: It uses the
Optional
class to handle the map with thenull
value.
5. Create Tests
In this step, I will utilize Junit tests to test the flattening list of maps methods created at step 4.
5.1 Create Demo Data
This is a mocked data model which contains a map object. We will use it to construct a list of maps.
DemoData.java
package org.zheng.demo; import java.util.Map; import java.util.Objects; public class DemoData { private Map<String, String> items; private String name; @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; DemoData other = (DemoData) obj; return Objects.equals(items, other.items) && Objects.equals(name, other.name); } public Map<String, String> getItems() { return items; } public String getName() { return name; } @Override public int hashCode() { return Objects.hash(items, name); } public void setItems(Map<String, String> items) { this.items = items; } public void setName(String name) { this.name = name; } }
5.2 Demo FlatStringMaps via Test
In this step, I will create a DemoFlatObjectMapsTest
class which tests the flatten methods created at step 4.2 with mocked testing data.
DemoFlatObjectMapsTest.java
package org.zheng.demo; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; class DemoFlatStringMapsTest { private DemoFlatStringMaps flatStringMaps = new DemoFlatStringMaps(); private List<Map<String, String>> setupNoDuplicateTestData() { Map<String, String> map1 = new HashMap<>(); map1.put("map1Key", "value13"); Map<String, String> map2 = new HashMap<>(); map2.put("map2Key", "value24"); List<Map<String, String>> maps = new ArrayList<>(); maps.add(map1); maps.add(map2); return maps; } private List<Map<String, String>> setupNullTestData() { Map<String, String> map1 = new HashMap<>(); map1.put("map1Key", null); Map<String, String> map2 = new HashMap<>(); map2.put("map2Key", "value24"); List<Map<String, String>> maps = new ArrayList<>(); maps.add(map1); maps.add(map2); return maps; } private List<Map<String, String>> setupTestData() { Map<String, String> map1 = new HashMap<>(); map1.put("sameKeySameValue", "value"); map1.put("sameKeyDiffValue", "value12"); map1.put("map1Key", "value13"); Map<String, String> map2 = new HashMap<>(); map2.put("sameKeySameValue", "value"); map2.put("sameKeyDiffValue", "value23"); map2.put("map2Key", "value24"); List<Map<String, String>> maps = new ArrayList<>(); maps.add(map1); maps.add(map2); return maps; } private List<DemoData> setupTestDemoDatas() { Map<String, String> map1 = new HashMap<>(); map1.put("sameKeySameValue", "value"); map1.put("sameKeyDiffValue", "value12"); map1.put("map1Key", "value13"); Map<String, String> map2 = new HashMap<>(); map2.put("sameKeySameValue", "value"); map2.put("sameKeyDiffValue", "value23"); map2.put("map2Key", "value24"); Map<String, String> map3 = new HashMap<>(); map3.put("sameKeySameValue", "value"); map3.put("sameKeyDiffValue", "value33"); map3.put("map3Key", "value44"); DemoData demo1 = new DemoData(); demo1.setName("Mary"); demo1.setItems(map1); DemoData demo2 = new DemoData(); demo2.setName("Zheng"); demo2.setItems(map2); DemoData demo3 = new DemoData(); demo3.setName("Zheng"); demo3.setItems(map3); List<DemoData> demoDatas = new ArrayList<>(); demoDatas.add(demo1); demoDatas.add(demo2); demoDatas.add(demo3); return demoDatas; } @Test void test_with_filter_DemoData() { List<DemoData> demoDatas = setupTestDemoDatas(); List<Map<String, String>> maps = demoDatas.stream().filter(demo -> demo.getName().startsWith("Zheng")) .map(demo -> demo.getItems()).collect(Collectors.toList()); Map<String, String> flattedMap = flatStringMaps.with_putAll(maps); assertEquals(4, flattedMap.size()); // {sameKeyDiffValue=value33, sameKeySameValue=value, map2Key=value24, // map3Key=value44} assertEquals("value33", flattedMap.get("sameKeyDiffValue")); assertEquals("value", flattedMap.get("sameKeySameValue")); assertEquals("value44", flattedMap.get("map3Key")); assertEquals("value24", flattedMap.get("map2Key")); } @Test void test_with_flatMap() { List<Map<String, String>> maps = setupNoDuplicateTestData(); Map<String, String> flattedMap = flatStringMaps.with_flatMap(maps); assertEquals(2, flattedMap.size()); // {map1Key=value13, map2Key=value24} assertEquals("value13", flattedMap.get("map1Key")); assertEquals("value24", flattedMap.get("map2Key")); } @Test void test_with_flatMap_andMerge() { List<Map<String, String>> maps = setupTestData(); Map<String, String> flattedMap = flatStringMaps.with_flatMap_andMerge(maps); assertEquals(4, flattedMap.size()); // {sameKeyDiffValue=value12, sameKeySameValue=value, map1Key=value13, // map2Key=value24} assertEquals("value12", flattedMap.get("sameKeyDiffValue")); assertEquals("value", flattedMap.get("sameKeySameValue")); assertEquals("value13", flattedMap.get("map1Key")); assertEquals("value24", flattedMap.get("map2Key")); } @Test void test_with_flatMap_error() { List<Map<String, String>> maps = setupTestData(); assertThrows(IllegalStateException.class, () -> { flatStringMaps.with_flatMap(maps); }); } @Test void test_with_flatMap_null() { List<Map<String, String>> maps = setupNullTestData(); assertThrows(NullPointerException.class, () -> { flatStringMaps.with_flatMap(maps); }); assertThrows(NullPointerException.class, () -> { flatStringMaps.with_putAll(maps); }); } @Test void test_with_putAll() { List<Map<String, String>> maps = setupTestData(); Map<String, String> flattedMap = flatStringMaps.with_putAll(maps); assertEquals(4, flattedMap.size()); // {sameKeyDiffValue=value23, sameKeySameValue=value, map1Key=value13, // map2Key=value24} assertEquals("value23", flattedMap.get("sameKeyDiffValue")); assertEquals("value", flattedMap.get("sameKeySameValue")); assertEquals("value13", flattedMap.get("map1Key")); assertEquals("value24", flattedMap.get("map2Key")); } @Test void test_with_putAll_DemoData() { List<DemoData> demoDatas = setupTestDemoDatas(); List<Map<String, String>> maps = demoDatas.stream().map(demo -> demo.getItems()).collect(Collectors.toList()); Map<String, String> flattedMap = flatStringMaps.with_putAll(maps); assertEquals(5, flattedMap.size()); // {sameKeyDiffValue=value33, sameKeySameValue=value, map1Key=value13, // map2Key=value24, map3Key=value44} assertEquals("value33", flattedMap.get("sameKeyDiffValue")); assertEquals("value", flattedMap.get("sameKeySameValue")); assertEquals("value13", flattedMap.get("map1Key")); assertEquals("value24", flattedMap.get("map2Key")); assertEquals("value44", flattedMap.get("map3Key")); } }
- line 18: set up two maps with no duplicate key and no null value. The flatten methods work fine for this mock data.
- line 31: set up two maps, one map has a
null
value. It’s used bytest_with_flatMap_null
which throws an exception. - line 44: set up two maps with duplicate keys:
sameKeySameValue
andsameKeyDiffValue
. It’s used by bothtest_with_flatMap_andMerge works
andtest_with_flatMap_error
. The test results confirm thatCollectors.toMap
with amergeFunction
can handle the duplicate key. - line 61: set up a list of
DemoData
which contains a map object. - line 98, 100, 101:
test_with_filter_DemoData
tests flattening a list of maps created at line 100 and 101 via List ofDemoData
. - line 115:
test_with_flatMap
works when the map has no duplicates and no null value. - line 126:
test_with_flatMap_andMerge
works when the map has nonull
value. - line 141:
test_with_flatMap_error
throws anIllegalStateException
when the map has duplicate keys. - line 151:
test_with_flatMap_null
throws aNullPointerException
when the map has anull
value.
Run the junit tests and all tests are passed but we know that the null
value is not handled as it throws a exception.
5.3 Demo FlatObjectMaps via Test
As you see at step 5.2, if the list of String
maps contain null
values, then it throws a NullPointerException
. In this step, I will create a DemoFlatObjectMapsTest
class which tests the class DemoFlatObjectMaps
created at 4.3. Test confirms that It handles both duplicate key and null
value with the Map<String, Object>
type.
DemoFlatObjectMapsTest.java
package org.zheng.demo; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; class DemoFlatObjectMapsTest { private DemoFlatObjectMaps flatObjectMaps = new DemoFlatObjectMaps(); private List<Map<String, Object>> queryForList(String s) { final List<Map<String, Object>> result = new ArrayList<>(); Map<String, Object> map = new HashMap<>(); for (int i = 0; i < 10; i++) { map = new HashMap<>(); map.put("key", "key" + i); map.put("value", "value" + i); result.add(map); } map = new HashMap<>(); map.put("key", "key1"); map.put("value", "value20"); result.add(map); map = new HashMap<>(); map.put("key", "key2"); map.put("value", "value22"); result.add(map); return result; } private List<Map<String, Object>> setupNoDupTestData() { Map<String, Object> map1 = new HashMap<>(); map1.put("map1Key", "value13"); map1.put("map1KeyWithNullValue", null); Map<String, Object> map2 = new HashMap<>(); map2.put("map2Key", "value24"); map2.put("map2KeyWithNullValue", null); List<Map<String, Object>> maps = new ArrayList<>(); maps.add(map1); maps.add(map2); return maps; } private List<Map<String, Object>> setupTestData() { Map<String, Object> map1 = new HashMap<>(); map1.put("sameKeySameValue", "value"); map1.put("sameKeyDiffValue", "value12"); map1.put("map1Key", "value13"); map1.put("map1KeyWithNullValue", null); Map<String, Object> map2 = new HashMap<>(); map2.put("sameKeySameValue", "value"); map2.put("sameKeyDiffValue", "value23"); map2.put("map2Key", "value24"); map2.put("map2KeyWithNullValue", null); List<Map<String, Object>> maps = new ArrayList<>(); maps.add(map1); maps.add(map2); return maps; } @Test public void test_grouping_byKey() { List<Map<String, Object>> steps = queryForList("SELECT key, value FROM table"); Map<String, List<String>> result1 = steps.stream() .collect(Collectors.groupingBy(k -> String.valueOf(k.get("key")), Collectors.mapping(l -> String.valueOf(l.get("value")), Collectors.toList()))); result1.entrySet().forEach(e -> System.out.println(e.getKey() + " -> " + e.getValue())); } @SuppressWarnings("unchecked") @Test void test_with_flatMap_andMerge_optional() { List<Map<String, Object>> maps = setupTestData(); Map<String, Object> flattedMap = flatObjectMaps.with_flatMap_andMerge_optional(maps); assertEquals(6, flattedMap.size()); assertEquals("value", ((Optional<String>) flattedMap.get("sameKeySameValue")).get()); assertEquals("value12", ((Optional<String>) flattedMap.get("sameKeyDiffValue")).get()); assertEquals("value13", ((Optional<String>) flattedMap.get("map1Key")).get()); assertEquals("value24", ((Optional<String>) flattedMap.get("map2Key")).get()); assertTrue(((Optional<String>) flattedMap.get("map1KeyWithNullValue")).isEmpty()); assertTrue(((Optional<String>) flattedMap.get("map2KeyWithNullValue")).isEmpty()); } @SuppressWarnings("unchecked") @Test void test_with_flatMap_andMerge_optional_l() { List<Map<String, Object>> maps = setupTestData(); Map<String, Object> flattedMap = flatObjectMaps.with_flatMap_andMerge_optional_l(maps); // {sameKeyDiffValue=Optional[value12], sameKeySameValue=Optional[value], // map1Key=Optional[value13], map2KeyWithNullValue=Optional.empty, // map2Key=Optional[value24], map1KeyWithNullValue=Optional.empty} assertEquals(6, flattedMap.size()); assertEquals("value", ((Optional<String>) flattedMap.get("sameKeySameValue")).get()); assertEquals("value12", ((Optional<String>) flattedMap.get("sameKeyDiffValue")).get()); assertEquals("value13", ((Optional<String>) flattedMap.get("map1Key")).get()); assertEquals("value24", ((Optional<String>) flattedMap.get("map2Key")).get()); assertTrue(((Optional<String>) flattedMap.get("map1KeyWithNullValue")).isEmpty()); assertTrue(((Optional<String>) flattedMap.get("map2KeyWithNullValue")).isEmpty()); } @Test void test_with_flatMap_error() { List<Map<String, Object>> maps = setupTestData(); assertThrows(IllegalStateException.class, () -> { flatObjectMaps.with_flatMap_optional(maps); }); } @Test void test_with_flatMap_null() { List<Map<String, Object>> maps = setupNoDupTestData(); assertThrows(NullPointerException.class, () -> { flatObjectMaps.with_flatMap(maps); }); } @SuppressWarnings("unchecked") @Test void test_with_flatMap_optional() { List<Map<String, Object>> maps = setupNoDupTestData(); Map<String, Object> flattedMap = flatObjectMaps.with_flatMap_optional(maps); assertEquals(4, flattedMap.size()); // {map1Key=Optional[value13], map2Key=Optional[value24], // map2KeyWithNullValue=Optional.empty, map1KeyWithNullValue=Optional.empty} assertEquals("value13", ((Optional<String>) flattedMap.get("map1Key")).get()); assertEquals("value24", ((Optional<String>) flattedMap.get("map2Key")).get()); assertTrue(((Optional<String>) flattedMap.get("map1KeyWithNullValue")).isEmpty()); assertTrue(((Optional<String>) flattedMap.get("map2KeyWithNullValue")).isEmpty()); } @SuppressWarnings("unchecked") @Test void test_with_putAll() { List<Map<String, Object>> maps = setupTestData(); Map<String, Object> flattedMap = flatObjectMaps.with_putAll(maps); assertEquals(6, flattedMap.size()); assertEquals("value", ((Optional<String>) flattedMap.get("sameKeySameValue")).get()); assertEquals("value23", ((Optional<String>) flattedMap.get("sameKeyDiffValue")).get()); assertEquals("value13", ((Optional<String>) flattedMap.get("map1Key")).get()); assertEquals("value24", ((Optional<String>) flattedMap.get("map2Key")).get()); assertTrue(((Optional<String>) flattedMap.get("map1KeyWithNullValue")).isEmpty()); assertTrue(((Optional<String>) flattedMap.get("map2KeyWithNullValue")).isEmpty()); } }
- line 30, 35: set up 2 duplicate keys and will be used at
test_grouping_byKey
for grouping method. - line 61, 63, 66, 67, 69: set up the duplicate key, null value mock data.
- line 79: this test uses
Collectors.groupingBy
to group the map data based on the same key. As you seen at the outputs, thekey1
has two values:value1
andvalue20
. thekey2
also has two values:value2
andvalue22
which matches the data set up at thequeryForList
method. - line 92:
test_with_flatMap_andMerge_optional
tests that flattening list of maps works for both duplicate key andnull
value. - line 110:
test_with_flatMap_andMerge_optional_l
tests and confirms that flattening list of maps works for both duplicate key and null value.
test_grouping_byKey outputs
key1 -> [value1, value20] key2 -> [value2, value22] key0 -> [value0] key5 -> [value5] key6 -> [value6] key3 -> [value3] key4 -> [value4] key9 -> [value9] key7 -> [value7] key8 -> [value8]
6. Handling Duplicate Keys
As you see at test_with_flatMap_error
, if the list of maps contains duplicate keys, then it will throw IllegalStateException
. We handle it by utilizing the Collectors.toMap
method by passing mergeFunction
.
with_flatMap_andMerge_optional.java
public Map<String, Object> with_flatMap_andMerge_optional(final List<Map<String, Object>> listMaps) { BinaryOperator<Object> mergeFunction = (existingValue, newValue) -> mergeValue(existingValue, newValue); Map<String, Object> flattenedMap = listMaps.stream().flatMap(map -> map.entrySet().stream()).collect(Collectors .toMap(entry -> entry.getKey(), entry -> Optional.ofNullable(entry.getValue()), mergeFunction)); return flattenedMap; } }
7. Handling null Values
As you see at test_with_flatMap_null
, if the list of maps contains a null
value, then it throw NullPointerException
. We will use the Optional.ofNullable
to avoid the NullPointerException
.
with_flatMap_andMerge_optional.java
public Map<String, Object> with_flatMap_andMerge_optional(final List<Map<String, Object>> listMaps) { BinaryOperator<Object> mergeFunction = (existingValue, newValue) -> mergeValue(existingValue, newValue); Map<String, Object> flattenedMap = listMaps.stream().flatMap(map -> map.entrySet().stream()).collect(Collectors .toMap(entry -> entry.getKey(), entry -> Optional.ofNullable(entry.getValue()), mergeFunction)); return flattenedMap; }
Here is the screenshot of Junit tests results.
8. Conclusion
In this tutorial, l created two Java classes to flatten a stream of maps into a single map with Stream.flatMap()
and Collectors.toMap()
methods. In order to demonstrate java flatten stream map usage, I mocked test data in the Junit tests and examined each transformation. This example also showed how null
and duplicate values are handled during the transformation.
9. Download
This was an example of flatten a Stream of Maps to a single map in a Java maven project.
You can download the full source code of this example here: Flatten a Stream of Maps to a Single Map in Java