Core Java

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 CaseUse Case Description
Configuration PropertiesApplications 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 ResultsApplications 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 DocumentsApplications need to convert a stream of JSON documents containing key-value pairs into a single map object.
Configuration Properties OverridesSystems 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 APIsApplications need to convert API returned data in the form of maps representing different attributes or properties into a single map.
Table 1 Use Cases of Combining list of Maps 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:

1
<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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    <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>
 
        <!--
        <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 – utilize Stream.flatMap and Collectors.toMap. This method will throw a NullPointerException if the mapEntry‘s value is null. It also throws an IllegalStateException if there are duplicate keys.
  • with_flatMap_andMerge – utilize Stream.flatMap and Collectors.toMap. This method will throw a NullPointerException if the mapEntry‘s value is null.

DemoFlatStringMaps.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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 calling Collectors.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 using Object data type. It also utilizes Stream.flatMap and Collectors.toMap. This method will throw a NullPointerExceptionif the mapEntry‘s value is null. It also throws an IllegalStateException if there are duplicate keys.
  • with_flatMap_optional – this method uses Optional to handle the null value object.
  • with_flatMap_andMerge_optional – this method uses Optional to handle null values and a mergeFunction to handle the duplicate keys.
  • with_flatMap_andMerge_optional_l – this method is the same as with_flatMap_andMerge_optional. but using a lambda expression instead of mergeFunction.

Both with_flatMap_andMerge_optional and with_flatMap_andMerge_optional_l are the robustest methods when flattening a list of maps.

DemoFlatObjectMaps.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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 calling Collectors.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 the null 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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
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 by test_with_flatMap_null which throws an exception.
  • line 44: set up two maps with duplicate keys: sameKeySameValue and sameKeyDiffValue. It’s used by both test_with_flatMap_andMerge works and test_with_flatMap_error. The test results confirm that Collectors.toMap with a mergeFunction 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 of DemoData.
  • 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 no null value.
  • line 141: test_with_flatMap_error throws an IllegalStateException when the map has duplicate keys.
  • line 151: test_with_flatMap_null throws a NullPointerException when the map has a null 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

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
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, the key1 has two values: value1 and value20. the key2 also has two values: value2 and value22 which matches the data set up at the queryForList method.
  • line 92: test_with_flatMap_andMerge_optional tests that flattening list of maps works for both duplicate key and null 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

01
02
03
04
05
06
07
08
09
10
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

1
2
3
4
5
6
7
8
9
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

1
2
3
4
5
6
7
8
9
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.

Figure 1 Test 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.

Download
You can download the full source code of this example here: Flatten a Stream of Maps to a Single Map in Java

Mary Zheng

Mary graduated from the Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She worked as a lead Software Engineer where she led and worked with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest


This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button