I have two maps whose keys are String
s and whose values are Set<MyObject>
. Given two Map
s, what is the easiest way to merge them such that if two keys are identical, the value is a union of the two sets. You can assume values are never null and if it is useful, we can make these Map
s SortedMap
s.

- 50,430
- 22
- 93
- 142

- 15,639
- 133
- 442
- 830
-
3If you have the possiblity to use Guavas [Multimap](https://code.google.com/p/guava-libraries/wiki/NewCollectionTypesExplained#Multimap), you can simply avoid the problem, and merging is as simple as putAll(Multimap other). – Dag May 29 '15 at 15:22
-
similar, possibly duplicate: http://stackoverflow.com/questions/4299728/how-can-i-combine-two-hashmap-objects-containing-the-same-types – Alec Gilliland Aug 20 '15 at 18:20
-
Should be easy to do with the [Map merge](https://docs.oracle.com/javase/8/docs/api/java/util/Map.html#merge-K-V-java.util.function.BiFunction-) method. – Roland Aug 29 '16 at 16:29
11 Answers
You can do this with a stream fairly easily:
Map<T, Set<U>> merged = Stream.of(first, second)
.map(Map::entrySet)
.flatMap(Set::stream)
.collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> {
HashSet<U> both = new HashSet<>(a);
both.addAll(b);
return both;
}));
This splits the maps into their Entry
s and then joins them with a Collector
which resolves duplicates by adding both values to a new HashSet
.
This also works for any number of maps.
Some variations which produce the same result:
Stream.of(first, second).flatMap(m -> m.entrySet().stream())
.collect(...);
Stream.concat(first.entrySet().stream(), second.entrySet().stream())
.collect(...); //from comment by Aleksandr Dubinsky
The third parameter for Collectors.toMap
is not necessary if there are no duplicate keys.
There is another Collectors.toMap
with a fourth parameter that lets you decide the type of the Map
collected into.

- 14,957
- 5
- 49
- 49
-
7A bit more concise is to use `Stream.concat (first.entrySet().stream(), second.entrySet().stream())` and avoid `map` and `flatMap`. – Aleksandr Dubinsky Mar 03 '15 at 11:00
Are we talking about HashMap
instances. In that case lookup is O(1), so you can just take one map, iterate over the entries of that map, see whether the other map contains that key. If not, just add the set. If it contains the key, take the union of the two sets (by adding all elements of one set to another)
To illustrate with some code, where I used a Set to have autocompletion in my IDE
Map<String, Set<Double>> firstMap = new HashMap<String, Set<Double>>( );
Map<String, Set<Double>> secondMap = new HashMap<String, Set<Double>>( );
Set<Map.Entry<String, Set<Double>>> entries = firstMap.entrySet();
for ( Map.Entry<String, Set<Double>> entry : entries ) {
Set<Double> secondMapValue = secondMap.get( entry.getKey() );
if ( secondMapValue == null ) {
secondMap.put( entry.getKey(), entry.getValue() );
}
else {
secondMapValue.addAll( entry.getValue() );
}
}

- 36,233
- 5
- 47
- 99
-
4
-
-
you can use - addAll method http://download.oracle.com/javase/6/docs/api/java/util/HashMap.html But there is always this issue that - if your two hash maps have any key same - then it will override the value of the key from first hash map with the value of the key from second hash map. For being on safer side - change the key values - you can use prefix or suffix on the keys - ( different prefix/suffix for first hash map and different prefix/suffix for second hash map ) – Ashish Shetkar Mar 16 '16 at 06:25
static void mergeSet(Map<String, Set<String>> map1, Map<String, Set<String>> map2) {
map1.forEach((key1, value1) -> {
map2.merge(key1, value1, (key2, value2) -> key2).addAll(value1);
});
}

- 71
- 1
- 3
-
That is not totally right.. merge function signature is more like `merge(T key, V value, Bifunction
` . so the right answer should be something like `map1.forEach((key1, value1) -> { map2.merge(key1, value1, (valu1, value2) -> doSomethingWithValues); }); – kszosze Sep 22 '20 at 09:03
How about this (untested):
Map<String,Set<Whatever>> m1 = // input map
Map<String,Set<Whatever>> m2 = // input map
Map<String,Set<Whatever>> ret = // new empty map
ret.putAll(m1);
for(String key : m2.keySet()) {
if(ret.containsKey(key)) {
ret.get(key).addAll(m2.get(key));
} else {
ret.put(key,m2.get(key));
}
}
This solution doesn't modify the input maps, and because it is short and relies on API methods only, I find it quite readable.
Note that putAll()
and addAll()
are both optional methods in Map
and Set
. Consequently (and in order to get O(1) lookup), I'd recommend using HashMap
and HashSet
.
Note that because neither HashSet
or HashMap
are synchronised you will need to look for some other solution if you want thread-safe code.

- 21,495
- 6
- 60
- 90
The following should merge a map1
into map2
(untested):
for (Entry<String, Set<???>> entry : map1.entrySet( ))
{
Set<???> otherSet = map2.get(entry.getKey( ));
if (otherSet == null)
map2.put(entry.getKey( ), entry.getValue ( ));
else
otherSet.addAll(entry.getValue( ));
}
I don't know what you've parameterized your Set
s on, hence the <???>
: replace as appropriate.

- 14,615
- 9
- 62
- 80
Something like this (untested):
// Assume all maps are of the same generic type.
public static Map<String, Set<MyObject>> mergeAll(Map m1, Map m2) {
Map<String, Set<MyObject>> merged = new HashMap();
// Merge commom entries into the new map.
for (Map.Entry<String, Set<MyObject>> entry : m1.entrySet()) {
String key = entry.getKey();
Set<MyObject> s1 = new HashSet(entry.getValue());
Set<MyObject> s2 = m2.get(key);
if (s2 != null) s1.addAll(s2);
merged.put(key, s1);
}
// Add entries unique to m2 to the new map.
for (String key : m2.keys()) {
if (!s1.containsKey(key)) merged.put(key, new HashSet(m2.get(key)));
}
return merged;
}
Note that this solution does not mutate either of its arguments.

- 151,642
- 46
- 269
- 291
-
1
-
You call `m2.getValue()`, but `m2` is a `Map` and thus has no `getValue()` method. – Michael McGowan Jan 09 '12 at 22:40
-
@MichaelMcGowan: oh right, fixed that too (geez, see what happens when I try to code off the top of my head!) – maerics Jan 09 '12 at 22:43
Map<Integer,String> m1=new HashMap<Integer,String>();
Map<Integer,String> m2=new HashMap<Integer,String>();
m1.put(1,"one");
m1.put(2,"two");
m2.put(3,"three");
m2.put(2,"two");
Set<Integer> s=m2.keySet();
for(int i:s){
if(m1.get(i)==null){
m1.put(i,m2.get(i));
}
}
System.out.println(m1);

- 11
- 1
Note that all other answers will eventually augment the original sets which you might not want for all use cases, if you don't want that just use a third map as output and create a new set for each key
public static void merge2Maps(Map<String, Set<Double>> a, Map<String, Set<Double>> b, Map<String, Set<Double>> c){
for (Map.Entry<String, Set<Double>> entry : a.entrySet()) {
Set<Double> set = new HashSet<Double>();
c.put(entry.getKey(), set);
set.addAll(entry.getValue());
}
for (Map.Entry<String, Set<Double>> entry : b.entrySet()) {
String key = entry.getKey();
Set<Double> set = c.get(key);
if (set == null) {
set = new HashSet<Double>();
c.put(entry.getKey(), set);
}
set.addAll(entry.getValue());
}
}

- 5,334
- 3
- 21
- 29
If you want to end up with immutable data structures to prevent manipulation of your merged map and map's Set instances then you can take this approach. This solution uses Google's Guava library.
public <K,T> Map<K, Set<T>> mergeToImmutable (
final Map<K, Set<T>> left,
final Map<K, Set<T>> right)
{
return Maps.toMap(
Sets.union(
checkNotNull(left).keySet(),
checkNotNull(right).keySet()
),
new Function<K, Set<T>> () {
@Override
public Set<T> apply (K input) {
return ImmutableSet.<T>builder()
.addAll(MoreObjects.firstNonNull(left.get(input), Collections.<T>emptySet()))
.addAll(MoreObjects.firstNonNull(right.get(input), Collections.<T>emptySet()))
.build();
}
}
);
}

- 1,246
- 15
- 16
If you define a method to unite non-null Set
s as:
static <T> Set<T> union(Set<T>... sets) {
return Stream.of(sets)
.filter(s -> s != null)
.flatMap(Set::stream)
.collect(Collectors.toSet());
}
then merging two maps m1
and m2
having Set<V>
values can be performed as follows:
Map<String, V> merged
= union(m1.keySet(), m2.keySet())
.stream()
.collect(Collectors.toMap(k -> k, k -> union(m1.get(k), m2.get(k))));
Or even simpler:
Map<String, V> merged = new HashMap<>();
for (String k : union(m1.keySet(), m2.keySet())
merged.put(k, union(m1.get(k), m2.get(k)));

- 14,185
- 5
- 45
- 67
<K, V> Map<K, List<V>> mergeMapOfLists(Stream<Map<K, List<V>>> stream) {
return stream
.map(Map::entrySet) // convert each map to set of map's entries
.flatMap(Collection::stream) // convert each map entry to stream and flat them to one stream
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue,
(list1, list2) -> {
list1.addAll(list2);
return list1;
})); // convert stream to map; if key is duplicated execute merge fuction (append exisitng list with elements from new list)
}

- 3,320
- 1
- 34
- 37