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.flatMapandCollectors.toMap. This method will throw aNullPointerExceptionif themapEntry‘s value isnull. It also throws anIllegalStateExceptionif there are duplicate keys.with_flatMap_andMerge– utilizeStream.flatMapandCollectors.toMap. This method will throw aNullPointerExceptionif 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
existingvaluewhen duplcate data is found. Adjust logic based on the application needs. - line 20: Call
flatMapmethod to flatten a list of maps to a single map object. - line 21: Call
Collectors.toMapwith method reference syntax. - line 27: Create a
mergeFunctionto select the value when duplicate keys are encountered. - line 30: Use the
mergeFunctionwhen callingCollectors.toMapmethod, 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 usingObjectdata type. It also utilizesStream.flatMapandCollectors.toMap. This method will throw a NullPointerExceptionif themapEntry‘s value isnull. It also throws anIllegalStateExceptionif there are duplicate keys.with_flatMap_optional– this method usesOptionalto handle thenullvalue object.with_flatMap_andMerge_optional– this method usesOptionalto handlenullvalues and amergeFunctionto 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
mergeFunctionto set the desired value when duplicate keys are encountered. - line 24: Use the
mergeFunctionwhen callingCollectors.toMapmethod. - line 33: It is the same as line 24 except with the lambda expression.
- line 41: It uses the
Optionalclass to handle the map with thenullvalue.
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
nullvalue. It’s used bytest_with_flatMap_nullwhich throws an exception. - line 44: set up two maps with duplicate keys:
sameKeySameValueandsameKeyDiffValue. It’s used by bothtest_with_flatMap_andMerge worksandtest_with_flatMap_error. The test results confirm thatCollectors.toMapwith amergeFunctioncan handle the duplicate key. - line 61: set up a list of
DemoDatawhich contains a map object. - line 98, 100, 101:
test_with_filter_DemoDatatests flattening a list of maps created at line 100 and 101 via List ofDemoData. - line 115:
test_with_flatMapworks when the map has no duplicates and no null value. - line 126:
test_with_flatMap_andMergeworks when the map has nonullvalue. - line 141:
test_with_flatMap_errorthrows anIllegalStateExceptionwhen the map has duplicate keys. - line 151:
test_with_flatMap_nullthrows aNullPointerExceptionwhen the map has anullvalue.
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_byKeyfor grouping method. - line 61, 63, 66, 67, 69: set up the duplicate key, null value mock data.
- line 79: this test uses
Collectors.groupingByto group the map data based on the same key. As you seen at the outputs, thekey1has two values:value1andvalue20. thekey2also has two values:value2andvalue22which matches the data set up at thequeryForListmethod. - line 92:
test_with_flatMap_andMerge_optionaltests that flattening list of maps works for both duplicate key andnullvalue. - line 110:
test_with_flatMap_andMerge_optional_ltests 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


