Building a Map of Set using Java Streams I wrote a collector that would take a stream of Map.Entry&lt;Integer, MyObject&gt; which I want to make into Map&lt;Integer, Set&lt;MyObject&gt;&gt;

import java.util.*;
import java.util.concurrent.*;
import java.util.stream.*;
class MyObject {

}
class Main {
  public static void main(String[] args) {
    Map&lt;Integer, Set&lt;MyObject&gt;&gt; collected = Stream.of(
       Map.entry(123, new MyObject()),
       Map.entry(123, new MyObject()),
       Map.entry(234, new MyObject()),
       Map.entry(345, new MyObject())
    )
    .collect(
      ConcurrentHashMap&lt;Integer, Set&lt;MyObject&gt;&gt;::new,
      (map, entry) -&gt; {
        map.computeIfAbsent(
          entry.getKey(), 
          k -&gt; new HashSet&lt;MyObject&gt;()
        )
        .add(entry.getValue());
      },
      (map1, map2) -&gt; {
        map2.forEach((map2Key, map2Object) -&gt; {
          final Set&lt;MyObject&gt; objects = 
            map1.computeIfAbsent(map2Key, k -&gt; new HashSet&lt;MyObject&gt;());
          objects.addAll(map2Object);
        });
      }
    );
    System.out.println(collected);
  }
}


I was wondering if there's a built in way of doing this, as it seems to be a common thing to do.

One way I can refactor this is to move the collector out to a different class like so:

class EntryToMapCollector&lt;K, V&gt; implements Collector&lt;Map.Entry&lt;K, V&gt;, Map&lt;K, Collection&lt;V&gt;&gt;, Map&lt;K, Collection&lt;V&gt;&gt;&gt; {
    @Override
    public Supplier&lt;Map&lt;K, Collection&lt;V&gt;&gt;&gt; supplier() {
        return ConcurrentHashMap&lt;K, Collection&lt;V&gt;&gt;::new;
    }

    @Override
    public BiConsumer&lt;Map&lt;K, Collection&lt;V&gt;&gt;, Map.Entry&lt;K, V&gt;&gt; accumulator() {
        return (map, entry) -&gt; {
            map.computeIfAbsent(entry.getKey(), k -&gt; new HashSet&lt;V&gt;())
                .add(entry.getValue());
        };
    }

    @Override
    public BinaryOperator&lt;Map&lt;K, Collection&lt;V&gt;&gt;&gt; combiner() {
        return (map1, map2) -&gt; {
            final Map&lt;K, Collection&lt;V&gt;&gt; combined = new ConcurrentHashMap&lt;&gt;(map1);
            map2.forEach((map2Key, map2Observations) -&gt; {
                combined.computeIfAbsent(map2Key, k -&gt; new HashSet&lt;V&gt;())
                    .addAll(map2Observations);
            });
            return combined;
        };
    }

    @Override
    public Function&lt;Map&lt;K, Collection&lt;V&gt;&gt;, Map&lt;K, Collection&lt;V&gt;&gt;&gt; finisher() {
        return m -&gt; m;
    }

    @Override
    public Set&lt;Characteristics&gt; characteristics() {
        return Set.of(Characteristics.CONCURRENT, Characteristics.UNORDERED);
    }
}


and replace the call with .collect(new EntryToMapCollector&lt;Integer, MyObject&gt;());

https://repl.it/@trajano/ForkedSpryRobot
Have you looked at .groupingBy() and .toSet()?

Map&lt;Integer, Set&lt;MyObject&gt;&gt; collected = Stream.of(...)
    .collect(Collectors.groupingBy(Map.Entry::getKey,
                                   Collectors.mapping(Map.Entry::getValue, Collectors.toSet())));


You possibly want .groupingByConcurrent() instead, though it is not exactly clear why.

Not a repl.it, but a JShell log:

U:\&gt;jshell
|  Welcome to JShell -- Version 13
|  For an introduction type: /help intro

jshell&gt; class MyObject { }
|  created class MyObject

jshell&gt; Map&lt;Integer, Set&lt;MyObject&gt;&gt; result = Stream.of(
   ...&gt;    Map.entry(123, new MyObject()),
   ...&gt;    Map.entry(123, new MyObject()),
   ...&gt;    Map.entry(234, new MyObject())
   ...&gt; ).collect(
   ...&gt;    Collectors.groupingByConcurrent(
   ...&gt;       Map.Entry::getKey,
   ...&gt;       Collectors.mapping(
   ...&gt;          Map.Entry::getValue,
   ...&gt;          Collectors.toSet()
   ...&gt;       )
   ...&gt;    )
   ...&gt; );
result ==&gt; {234=[MyObject@78c03f1f], 123=[MyObject@5ce81285, MyObject@52a86356]}

jshell&gt;