1

So I'm trying to use the Java 8 streams Collectors.toMap to add elements to a newly created Map.

The Map to be created may contain null values for some of its keys. This is perfectly acceptable for a HashMap, and when I use a stream forEach to add member it works as expected:

    final Map<String, Method> rMap = new HashMap<> ( );
    msgRouteProps.entrySet ( )
                 .stream ( )
                 .forEach (entry -> rMap.put (entry.getKey ( ),
                                              ReflectionUtil.getNamedMethod (processor, entry.getValue ( ))));

The msgRouteProps is a Map where the keys and values are all non-null. Note that the ReflectionUtil.getNamedMethod () call may return a null, which I want to be put into the resulting Map.

So I wanted to explore how to do the same thing with the Collectors.toMap() method, so the stream operation could return the newly created map, without me having to create it directly.

So I tried the following:

Map<String, Method> rMap = 
    msgRouteProps.entrySet ( )
                 .stream ( )
                 .collect (Collectors.toMap (Map.Entry::getKey,
                                             (e) -> ReflectionUtil.getNamedMethod (processor, e.getValue ( ))));

It seems to me that this should work the same way, but unfortunately, it fails with a null pointer exception. It appears that the Collectors.toMap method calls the HashMap's merge() method which doesn't allow null values to be passed. What seems odd to me is that I don't really think that merge should be getting called at all in this case, since the keys in the original map are all unique. Given that, I would assume that the values could simply be added to the new map via calling the map's put() method, which will allow null values.

Is there something that I'm doing wrong here, or is there a work-around for this? Or is this simply the expected behavior when using Collectors.toMap?

Steve
  • 2,678
  • 8
  • 40
  • 54

1 Answers1

2

It's a known bug already been reported by many users

The best way is to use forEach if you have null values. Still if you want to use collect then try the following work around

Map<String, Method> rMap = 
    msgRouteProps.entrySet()
                 .stream()
                 .collect(HashMap::new, (m,v) -> m.put(v.getKey(), ReflectionUtil.getNamedMethod(processor, v.getValue())), HashMap::putAll);

Also take a look at this answer as well

Nidhish Krishnan
  • 20,593
  • 6
  • 63
  • 76
  • Thanks for the pointer. I guess I'll just stick with the forEach until the bug is fixed. – Steve Aug 07 '19 at 10:08
  • @Steve I would not wait too long. Recent versions of `toMap` without a merge function do not use `Map.merge` at all, still, they reproduce the behavior of throwing an exception if the value is `null`. Using `forEach` here has a point, though it doesn’t make much sense to pollute a `forEach` based approach with the Stream API. `msgRouteProps.forEach((key,value) -> rMap.put(key, ReflectionUtil.getNamedMethod(processor, value)));` is way more readable. – Holger Aug 08 '19 at 17:32
  • @Holger Yeah, that's what I started with. I'm going to continue to use that, since it looks like the Collectors.toMap won't work for my needs. – Steve Aug 12 '19 at 14:07