NullPointerException: element cannot be mapped to a null key
Asked Answered
I

7

28

I have read the topic:

Collectors.groupingBy doesn't accept null keys

But I don't understand how can I apply it for my issue:

my code:

Map<String, List<MappingEntry>> mappingEntryMap = mapping.getMappingEntries()
                .stream()
                .collect(Collectors.groupingBy(MappingEntry::getMilestone, Collectors.mapping(e -> e, Collectors.toList())));

For me MappingEntry::getMilestone sometimes can return null. It is ok for my situation but I see:

Caused by: java.lang.NullPointerException: element cannot be mapped to a null key
    at java.util.Objects.requireNonNull(Objects.java:228)
    at java.util.stream.Collectors.lambda$groupingBy$45(Collectors.java:907)
    at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)

How can I avoid this exception?

Isolate answered 18/12, 2017 at 12:47 Comment(9)
how about filtering the nulls first?Diffident
@Diffident it is not allowable for my case. I must not lose the data. This approach described in the linked topicIsolate
@Isolate what about applying a default key for all null keys?Oatmeal
Use the oldschool way instead of streams?Glucoprotein
Also Collectors.mapping(e -> e, Collectors.toList()) can be simplified to Collectors.toList()Oatmeal
@Lino, it is correctIsolate
@Marvin, I undetstand, that I can, but i prefere to use streams. And it looks like stream should have this optionIsolate
@Oatmeal … and Collectors.toList() can be omitted when using Collectors.groupingBy.Sumption
Please checking my answer, solution using Optional instead of raw nullsBouncy
D
24

Use Collectors.toMap instead and specify that a HashMap is used (as it allows one null key)

 Collectors.toMap(
       MappingEntry::getMilestone,
       x -> {
           List<MappingEntry> list = new ArrayList<>();
           list.add(x);
           return list;
       },
       (left, right) -> {
            left.addAll(right);
            return left;
       },
       HashMap::new

)
Diffident answered 18/12, 2017 at 13:4 Comment(3)
Not a beautiful solution. You changed the data structure as well as the logic. Doesn't deserve to be the best answer.Filial
@RajeshPaul there are al least 17 people that disagree with you.Diffident
@RajeshPaul The data structure is still Map<String, List<MappingEntry>> and the logic is the same.Feathers
N
17

Given that you want to retain the MappingEntry objects regardless of when getMilestone() is null or non-null and knowing that a NullPointerException will be thrown when a particular contract in not met then we can avoid that by using a replacement key to group the MappingEntry objects which have a null milestone and yet group the other MappingEntry objects as they're supposed to be.

Map<String, List<MappingEntry>> mappingEntryMap = 
             mapping.getMappingEntries()
                    .stream()
                    .collect(groupingBy(m -> m.getMilestone() == null ?
                                  "absentMilestone" : m.getMilestone()));

The trick here is to use a ternary operator which provides a key to group all the MappingEntry objects that have a absent milestone into a single group and if the milestone is not absent then we can group by its value as you'd expect.

Nervine answered 18/12, 2017 at 13:33 Comment(2)
This is absolutely correct answer and deserves to be chosen as the best answer. Thanks @Ousmane D.Filial
@RajeshPaul This is absolutely not a correct answer. It changes the behavior of the original code in case getMilestone() returns "absentMilestone" -- in that case, both the objects for which "absentMilestone" is returned by getMilestone() and the ones for which getMilestone() returns null will end up in the same bucket.Feathers
B
2

In my opinion the best solution:

Use Optional.empty() instead of pasing null value.

Map<Optional<String>, List<MappingEntry>> mappingEntryMap = mapping.getMappingEntries()
            .stream()
            .collect(Collectors.groupingBy(Optional::ofNullable, Collectors.mapping(e -> e, Collectors.toList())));
Bouncy answered 5/11, 2021 at 10:10 Comment(1)
Ugh... What's the point of using HashMap, which supports null keys, if we still have to use Optional<X> keys because the stupid groupingBy throws NPE if classifier returns null? That check may as well have been omitted, it would have made everything easier with no loss of safety (NPE would still be thrown upon putting the null key in the map, if and only if the map doesn't support null keys). But unfortunately, this seems to be the best solution, followed by promptly converting the Map<Optional<X>> into a Map<X> with null keys.Feathers
T
2

Better to use Stream.collect():

mapping.getMappingEntries().stream()
  .collect(
    HashMap::new,
    (m,v) -> m.computeIfAbsent(v.getMilestone, tmp -> new ArrayList<>()).add(v),
    (m1, m2) -> {}
  );
Transpicuous answered 30/11, 2022 at 11:24 Comment(0)
R
1

Please use Optional for that:

var mappingEntryMap = mapping.getMappingEntries().stream()
    .collect(groupingBy(entry -> ofNullable(entry.getMilestone());

And then:

var emptyMilestoneList = mappingEntryMap.get(Optional.empty());
Realty answered 11/11, 2022 at 10:27 Comment(0)
O
0

You can use the Collector.of method for this to specify a custom collector and use a HashMap for the resulting map because according to the Java-8 docs of HashMap

[...] permits null values and the null key. [...]

Collector.of(
    HashMap::new, 
    (map, e) -> map.computeIfAbsent(e.getMileStone(), k -> new ArrayList<>()).add(e), 
    (left, right) -> {
         right.forEach((key,list) -> left.computeIfAbsent(key, k -> new ArrayList<>()).addAll(list));
         return left;
     })
)
Oatmeal answered 18/12, 2017 at 13:9 Comment(0)
A
0

use filter and get only not null data.

like

            Map<String, List<Entity>> map = list
                .stream()
                .filter(entity -> entity.getZoneRefId()!=null)
                .collect(
                        Collectors.
                                groupingBy(
                                        Entity::getZoneName));
        
Ancilin answered 19/11, 2021 at 12:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.