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:

 <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 – 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

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

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

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

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

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.

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 in the telecommunications sector 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
Inline Feedbacks
View all comments
Back to top button