NullPointerException in Collectors.toMap with null entry values
Asked Answered
K

14

549

Collectors.toMap throws a NullPointerException if one of the values is null. I don't understand this behaviour, maps can contain null pointers as value without any problems. Is there a good reason why values cannot be null for Collectors.toMap?

Also, is there a nice Java 8 way of fixing this, or should I revert to plain old for loop?

An example of my problem:

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


class Answer {
    private int id;

    private Boolean answer;

    Answer() {
    }

    Answer(int id, Boolean answer) {
        this.id = id;
        this.answer = answer;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Boolean getAnswer() {
        return answer;
    }

    public void setAnswer(Boolean answer) {
        this.answer = answer;
    }
}

public class Main {
    public static void main(String[] args) {
        List<Answer> answerList = new ArrayList<>();

        answerList.add(new Answer(1, true));
        answerList.add(new Answer(2, true));
        answerList.add(new Answer(3, null));

        Map<Integer, Boolean> answerMap =
        answerList
                .stream()
                .collect(Collectors.toMap(Answer::getId, Answer::getAnswer));
    }
}

Stacktrace:

Exception in thread "main" java.lang.NullPointerException
    at java.util.HashMap.merge(HashMap.java:1216)
    at java.util.stream.Collectors.lambda$toMap$168(Collectors.java:1320)
    at java.util.stream.Collectors$$Lambda$5/1528902577.accept(Unknown Source)
    at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1359)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
    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)
    at Main.main(Main.java:48)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)

This problem still exists in Java 11.

Khelat answered 8/7, 2014 at 11:44 Comment(7)
null always was a bit problematic, like in TreeMap. Maybe a nice moment to try out Optional<Boolean>? Otherwise split and use filter.Pythia
@JoopEggen null could be a problem for a key, but in this case it's the value.Larrabee
Not all maps have problem with null, HashMap for example can have one null key and any number of null values, you can try creating a custom Collector using a HashMap instead of using the default one.Strander
@Strander But the default implementation is HashMap - as shown in the first line of stacktrace. The problem is not that a Map can't hold null value, but that the second argument of Map#merge function can't be null.Vasili
Personally, with the given circumstances, I would go with non-stream solution, or forEach() if the input is parallel. The nice short stream based solutions below could have a terrible performance.Ada
You have a better solution for your problem by @StranderCraft
I just discovered this today ! I really don't understand the use of Collectors.toMap() then ! It is like if with collect into a list and can't add null values. I fear that at several locations my code is failing for that.Florenceflorencia
S
528

You can work around this known bug in OpenJDK with this:

Map<Integer, Boolean> collect = list.stream()
        .collect(HashMap::new, (m,v)->m.put(v.getId(), v.getAnswer()), HashMap::putAll);

It is not that much pretty, but it works. Result:

1: true
2: true
3: null

(this tutorial helped me the most.)

EDIT:

Unlike Collectors.toMap, this will silently replace values if you have the same key multiple times, as @mmdemirbas pointed out in the comments. If you don't want this, look at the link in the comment.

Strander answered 8/7, 2014 at 14:9 Comment(11)
Nice example. Do you know if a similar syntax is available for TreeMap with passing a comparator to new?Ideogram
@Ideogram yes, a definition of a supplier (the first argument) is a function that passes no parameters and returns a result, thus the lambda for your case would be () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER) to create a case insensitive String keyed TreeMap.Tanika
This is the correct answer, and IMHO what the JDK should be doing for its default non-overloaded version instead. Maybe merge is faster, but I have not tested.Tanika
I had to specify the type parameters in order to compile, that way: Map<Integer, Boolean> collect = list.stream().collect(HashMap<Integer, Boolean>::new, (m,v)->m.put(v.getId(), v.getAnswer()), HashMap<Integer, Boolean>::putAll);. I had: incompatible types: cannot infer type-variable(s) R (argument mismatch; invalid method reference no suitable method found for putAll(java.util.Map<java.lang.Integer,java.lang.Boolean>,java.util.Map<java.lang.Integer,java.lang.Boolean>) method java.util.Map.putAll(java.util.Map) is not applicable (actual and formal argument lists differ in length)Emblazonment
This is a fancy way of @Larrabee solution (rephrasing the classical instructions to lambda expression) but works fine!..Thanks !Tody
This might be quite slow on a large input. You create a HashMap and then call putAll() for every single entry. Personally, at given circumstances, I would go with non-stream solution, or forEach() if the input is parallel.Ada
"and then call putAll() for every single entry" is that how it works? I thought that is used only if the stream is parralel to join the results.Strander
Map.merge can be avoided also by switching from toMap to Collectors.groupingBy but only when it's certain that there are no duplicate keys. Downside here is that you need to use Collectors.reduce or similar for the values instead of the simple method reference.Intransitive
@Strander you are correct. That comment is simply wrong unless you have one thread in your pool per element in the stream.Highbred
Beware that this solution behaves differently than the original toMap implementation. Original implementation detects duplicate keys and throws an IllegalStatException, but this solution silently accepts the latest key. Emmanuel Touzery's solution (https://mcmap.net/q/73467/-nullpointerexception-in-collectors-tomap-with-null-entry-values) is closer to the original behaviour.Copter
Here even newer Bug-Ticket which covers even Java 17.. bugs.openjdk.org/browse/JDK-8261865Doyledoyley
L
210

It is not possible with the static methods of Collectors. The javadoc of toMap explains that toMap is based on Map.merge:

@param mergeFunction a merge function, used to resolve collisions between values associated with the same key, as supplied to Map#merge(Object, Object, BiFunction)}

and the javadoc of Map.merge says:

@throws NullPointerException if the specified key is null and this map does not support null keys or the value or remappingFunction is null

You can avoid the for loop by using the forEach method of your list.

Map<Integer,  Boolean> answerMap = new HashMap<>();
answerList.forEach((answer) -> answerMap.put(answer.getId(), answer.getAnswer()));

but it is not really simple than the old way:

Map<Integer, Boolean> answerMap = new HashMap<>();
for (Answer answer : answerList) {
    answerMap.put(answer.getId(), answer.getAnswer());
}
Larrabee answered 8/7, 2014 at 12:37 Comment(13)
In that case I would rather use the old-fashioned for-each. Should I consider this a bug in toMerge? as the use of this merge function is really an implementation detail, or is the a good reasoning for not allowing toMap to process null values?Khelat
I don't see that in the javadoc, your quote is about a different overload of the toMap function. I don't provide a merge function. Anyway, thanks for your answer, a bit dissapointing for me that the old way is still the most attractive option (i had rather used the java-8 way :) ).Khelat
Yes it is from an overloaded toMap method. This method is the base method of the others ones, so i guess is documentation is more complete. But you are right, it is not explicitly specified in the method you are using.Larrabee
Never thought that null values in map would make such an impact on standard API, I'd rather consider it as a flaw.Brumley
Actually the API docs don't state anything about the use of Map.merge. This IMHO is a flaw in the implementation that restricts a perfectly acceptable use-case that has been overlooked. The overloaded methods of toMap do state the use of Map.merge but not the one the OP is using.Tanika
@BrettRyan it mentions it by the overloads that accept mergeFunction, but not by the first one. Regardless, it definitely seems like an oversight.Lochia
@Khelat there is even bug report bugs.openjdk.java.net/browse/JDK-8148463Tommy
This should not have been accepted as the answer. @Strander gave the right answer.Craft
I don't care what it has under the hood. All I care when reading Javadoc is the contract. Javadoc should say that it throws NullPointerException if any of the elements is null!Grater
@acv sir yes sir!Larrabee
This answer has better explanation .Icj
@AskarKalykov 100% agree. I noticed today that Map.ofEntries has the same flaw. For no reason that I can think of (except for laziness on the JDK developers' part because now they don't have to do null checks in their equals and hashCode methods) they are disallowing null values there, too.Expulsive
Yes, that's what the spec says. However, it looks like a typo in the spec, because it makes no sense. It should probably have read: throws NullPointerException if the specified key is null and this map does not support null keys or the value of remappingFunction is nullWonderland
O
32

I wrote a Collector which, unlike the default java one, does not crash when you have null values:

public static <T, K, U>
        Collector<T, ?, Map<K, U>> toMap(Function<? super T, ? extends K> keyMapper,
                Function<? super T, ? extends U> valueMapper) {
    return Collectors.collectingAndThen(
            Collectors.toList(),
            list -> {
                Map<K, U> result = new HashMap<>();
                for (T item : list) {
                    K key = keyMapper.apply(item);
                    if (result.putIfAbsent(key, valueMapper.apply(item)) != null) {
                        throw new IllegalStateException(String.format("Duplicate key %s", key));
                    }
                }
                return result;
            });
}

Just replace your Collectors.toMap() call to a call to this function and it'll fix the problem.

Occlusion answered 18/9, 2015 at 9:21 Comment(1)
But allowing null values and using putIfAbsent does not play well together. It does not detect duplicate keys when they map to nullBoat
W
13

Yep, a late answer from me, but I think it may help to understand what's happening under the hood in case anyone wants to code some other Collector-logic.

I tried to solve the problem by coding a more native and straight forward approach. I think it's as direct as possible:

public class LambdaUtilities {

  /**
   * In contrast to {@link Collectors#toMap(Function, Function)} the result map
   * may have null values.
   */
  public static <T, K, U, M extends Map<K, U>> Collector<T, M, M> toMapWithNullValues(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) {
    return toMapWithNullValues(keyMapper, valueMapper, HashMap::new);
  }

  /**
   * In contrast to {@link Collectors#toMap(Function, Function, BinaryOperator, Supplier)}
   * the result map may have null values.
   */
  public static <T, K, U, M extends Map<K, U>> Collector<T, M, M> toMapWithNullValues(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, Supplier<Map<K, U>> supplier) {
    return new Collector<T, M, M>() {

      @Override
      public Supplier<M> supplier() {
        return () -> {
          @SuppressWarnings("unchecked")
          M map = (M) supplier.get();
          return map;
        };
      }

      @Override
      public BiConsumer<M, T> accumulator() {
        return (map, element) -> {
          K key = keyMapper.apply(element);
          if (map.containsKey(key)) {
            throw new IllegalStateException("Duplicate key " + key);
          }
          map.put(key, valueMapper.apply(element));
        };
      }

      @Override
      public BinaryOperator<M> combiner() {
        return (left, right) -> {
          int total = left.size() + right.size();
          left.putAll(right);
          if (left.size() < total) {
            throw new IllegalStateException("Duplicate key(s)");
          }
          return left;
        };
      }

      @Override
      public Function<M, M> finisher() {
        return Function.identity();
      }

      @Override
      public Set<Collector.Characteristics> characteristics() {
        return Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));
      }

    };
  }

}

And the tests using JUnit and assertj:

  @Test
  public void testToMapWithNullValues() throws Exception {
    Map<Integer, Integer> result = Stream.of(1, 2, 3)
        .collect(LambdaUtilities.toMapWithNullValues(Function.identity(), x -> x % 2 == 1 ? x : null));

    assertThat(result)
        .isExactlyInstanceOf(HashMap.class)
        .hasSize(3)
        .containsEntry(1, 1)
        .containsEntry(2, null)
        .containsEntry(3, 3);
  }

  @Test
  public void testToMapWithNullValuesWithSupplier() throws Exception {
    Map<Integer, Integer> result = Stream.of(1, 2, 3)
        .collect(LambdaUtilities.toMapWithNullValues(Function.identity(), x -> x % 2 == 1 ? x : null, LinkedHashMap::new));

    assertThat(result)
        .isExactlyInstanceOf(LinkedHashMap.class)
        .hasSize(3)
        .containsEntry(1, 1)
        .containsEntry(2, null)
        .containsEntry(3, 3);
  }

  @Test
  public void testToMapWithNullValuesDuplicate() throws Exception {
    assertThatThrownBy(() -> Stream.of(1, 2, 3, 1)
        .collect(LambdaUtilities.toMapWithNullValues(Function.identity(), x -> x % 2 == 1 ? x : null)))
            .isExactlyInstanceOf(IllegalStateException.class)
            .hasMessage("Duplicate key 1");
  }

  @Test
  public void testToMapWithNullValuesParallel() throws Exception {
    Map<Integer, Integer> result = Stream.of(1, 2, 3)
        .parallel() // this causes .combiner() to be called
        .collect(LambdaUtilities.toMapWithNullValues(Function.identity(), x -> x % 2 == 1 ? x : null));

    assertThat(result)
        .isExactlyInstanceOf(HashMap.class)
        .hasSize(3)
        .containsEntry(1, 1)
        .containsEntry(2, null)
        .containsEntry(3, 3);
  }

  @Test
  public void testToMapWithNullValuesParallelWithDuplicates() throws Exception {
    assertThatThrownBy(() -> Stream.of(1, 2, 3, 1, 2, 3)
        .parallel() // this causes .combiner() to be called
        .collect(LambdaUtilities.toMapWithNullValues(Function.identity(), x -> x % 2 == 1 ? x : null)))
            .isExactlyInstanceOf(IllegalStateException.class)
            .hasCauseExactlyInstanceOf(IllegalStateException.class)
            .hasStackTraceContaining("Duplicate key");
  }

And how do you use it? Well, just use it instead of toMap() like the tests show. This makes the calling code look as clean as possible.

EDIT:
implemented Holger's idea below, added a test method

Wentz answered 8/4, 2017 at 19:27 Comment(2)
The combiner does not check for duplicate keys. If you want to avoid to check for every key, you can use something like (map1, map2) -> { int total = map1.size() + map2.size(); map1.putAll(map2); if(map1.size() < total.size()) throw new IllegalStateException("Duplicate key(s)"); return map1; }Boat
@Boat Yep, that's true. Especially since accumulator() actually does check that. Maybe I should do some parallel streams once :)Wentz
U
11

Here's somewhat simpler collector than proposed by @EmmanuelTouzery. Use it if you like:

public static <T, K, U> Collector<T, ?, Map<K, U>> toMapNullFriendly(
        Function<? super T, ? extends K> keyMapper,
        Function<? super T, ? extends U> valueMapper) {
    @SuppressWarnings("unchecked")
    U none = (U) new Object();
    return Collectors.collectingAndThen(
            Collectors.<T, K, U> toMap(keyMapper,
                    valueMapper.andThen(v -> v == null ? none : v)), map -> {
                map.replaceAll((k, v) -> v == none ? null : v);
                return map;
            });
}

We just replace null with some custom object none and do the reverse operation in the finisher.

Upkeep answered 18/9, 2015 at 9:52 Comment(0)
C
9

I have slightly modified Emmanuel Touzery's null-safe map Collector implementation.

This version:

  • Allows null keys
  • Allows null values
  • Detects duplicate keys (even if they are null) and throws IllegalStateException as in the original JDK implementation
  • Detects duplicate keys also when the key already mapped to the null value. In other words, separates a mapping with null-value from no-mapping
public static <T, K, U> Collector<T, ?, Map<K, U>> toMapOfNullables(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) {
    return Collectors.collectingAndThen(
        Collectors.toList(),
        list -> {
            Map<K, U> map = new LinkedHashMap<>();
            list.forEach(item -> {
                K key = keyMapper.apply(item);
                U value = valueMapper.apply(item);
                if (map.containsKey(key)) {
                    throw new IllegalStateException(String.format(
                            "Duplicate key %s (attempted merging values %s and %s)",
                            key, map.get(key), value));
                }
                map.put(key, value);
            });
            return map;
        }
    );
}

Unit tests:

@Test
public void toMapOfNullables_WhenHasNullKey() {
    assertEquals(singletonMap(null, "value"),
        Stream.of("ignored").collect(Utils.toMapOfNullables(i -> null, i -> "value"))
    );
}

@Test
public void toMapOfNullables_WhenHasNullValue() {
    assertEquals(singletonMap("key", null),
        Stream.of("ignored").collect(Utils.toMapOfNullables(i -> "key", i -> null))
    );
}

@Test
public void toMapOfNullables_WhenHasDuplicateNullKeys() {
    assertThrows(new IllegalStateException("Duplicate key null"),
        () -> Stream.of(1, 2, 3).collect(Utils.toMapOfNullables(i -> null, i -> i))
    );
}

@Test
public void toMapOfNullables_WhenHasDuplicateKeys_NoneHasNullValue() {
    assertThrows(new IllegalStateException("Duplicate key duplicated-key"),
        () -> Stream.of(1, 2, 3).collect(Utils.toMapOfNullables(i -> "duplicated-key", i -> i))
    );
}

@Test
public void toMapOfNullables_WhenHasDuplicateKeys_OneHasNullValue() {
    assertThrows(new IllegalStateException("Duplicate key duplicated-key"),
        () -> Stream.of(1, null, 3).collect(Utils.toMapOfNullables(i -> "duplicated-key", i -> i))
    );
}

@Test
public void toMapOfNullables_WhenHasDuplicateKeys_AllHasNullValue() {
    assertThrows(new IllegalStateException("Duplicate key duplicated-key"),
        () -> Stream.of(null, null, null).collect(Utils.toMapOfNullables(i -> "duplicated-key", i -> i))
    );
}
Copter answered 8/8, 2019 at 9:13 Comment(1)
Slight improvement to the duplicate message to mirror the one in Java 11, which also includes the duplicate's values: U value = valueMapper.apply(item); if (map.containsKey(key)) { throw new IllegalStateException(String.format("Duplicate key %s (attempted merging values %s and %s)", key, map.get(key), value)); } map.put(key, value); Theomorphic
C
4

If the value is a String, then this might work: map.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> Optional.ofNullable(e.getValue()).orElse("")))

Conventioner answered 27/4, 2016 at 20:36 Comment(1)
That works only if you are okay with modifying the data. Downstream methods might expect null values rather than empty strings.Glindaglinka
A
4

Sorry to reopen an old question, but since it was edited recently saying that the "issue" still remains in Java 11, I felt like I wanted to point out this:

answerList
        .stream()
        .collect(Collectors.toMap(Answer::getId, Answer::getAnswer));

gives you the null pointer exception because the map does not allow null as a value. This makes sense because if you look in a map for the key k and it is not present, then the returned value is already null (see javadoc). So if you were able to put in k the value null, the map would look like it's behaving oddly.

As someone said in the comments, it's pretty easy to solve this by using filtering:

answerList
        .stream()
        .filter(a -> a.getAnswer() != null)
        .collect(Collectors.toMap(Answer::getId, Answer::getAnswer));

in this way no null values will be inserted in the map, and STILL you will get null as the "value" when looking for an id that does not have an answer in the map.

I hope this makes sense to everyone.

Aquiver answered 7/11, 2018 at 7:50 Comment(1)
It would make sense if a map did not allow null values, but it does. You can do answerMap.put(4, null); without any problems. You are right that with your proposed solution you will get the same result for anserMap.get() if it is not present as if the value would be inserted as null. However, if you iterate over all entries of the map there is a obviously a difference.Khelat
S
4
public static <T, K, V> Collector<T, HashMap<K, V>, HashMap<K, V>> toHashMap(
        Function<? super T, ? extends K> keyMapper,
        Function<? super T, ? extends V> valueMapper
)
{
    return Collector.of(
            HashMap::new,
            (map, t) -> map.put(keyMapper.apply(t), valueMapper.apply(t)),
            (map1, map2) -> {
                map1.putAll(map2);
                return map1;
            }
    );
}

public static <T, K> Collector<T, HashMap<K, T>, HashMap<K, T>> toHashMap(
        Function<? super T, ? extends K> keyMapper
)
{
    return toHashMap(keyMapper, Function.identity());
}
Savagery answered 24/1, 2019 at 19:27 Comment(1)
upvoting because this compiles. Accepted answer does not compile because Map::putAll does not have a return value.Highbred
J
3

According to the Stacktrace

Exception in thread "main" java.lang.NullPointerException
at java.util.HashMap.merge(HashMap.java:1216)
at java.util.stream.Collectors.lambda$toMap$148(Collectors.java:1320)
at java.util.stream.Collectors$$Lambda$5/391359742.accept(Unknown Source)
at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1359)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
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)
at com.guice.Main.main(Main.java:28)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)

When is called the map.merge

        BiConsumer<M, T> accumulator
            = (map, element) -> map.merge(keyMapper.apply(element),
                                          valueMapper.apply(element), mergeFunction);

It will do a null check as first thing

if (value == null)
    throw new NullPointerException();

I don't use Java 8 so often so i don't know if there are a better way to fix it, but fix it is a bit hard.

You could do:

Use filter to filter all NULL values, and in the Javascript code check if the server didn't send any answer for this id means that he didn't reply to it.

Something like this:

Map<Integer, Boolean> answerMap =
        answerList
                .stream()
                .filter((a) -> a.getAnswer() != null)
                .collect(Collectors.toMap(Answer::getId, Answer::getAnswer));

Or use peek, which is used to alter the stream element for element. Using peek you could change the answer to something more acceptable for map but it means edit your logic a bit.

Sounds like if you want to keep the current design you should avoid Collectors.toMap

Jurado answered 8/7, 2014 at 12:38 Comment(0)
B
2

Retaining all questions ids with small tweak

Map<Integer, Boolean> answerMap = 
  answerList.stream()
            .collect(Collectors.toMap(Answer::getId, a -> 
                       Boolean.TRUE.equals(a.getAnswer())));
Breannabreanne answered 23/6, 2017 at 8:6 Comment(2)
I think this is the best answer - it's the most concise answer and it fixes the NPE issue.Gerfen
Trick is equals() handles null so above is enoughBreannabreanne
D
2

For completeness, I'm posting a version of the toMapOfNullables with a mergeFunction param:

public static <T, K, U> Collector<T, ?, Map<K, U>> toMapOfNullables(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction) {
    return Collectors.collectingAndThen(Collectors.toList(), list -> {
        Map<K, U> result = new HashMap<>();
        for(T item : list) {
            K key = keyMapper.apply(item);
            U newValue = valueMapper.apply(item);
            U value = result.containsKey(key) ? mergeFunction.apply(result.get(key), newValue) : newValue;
            result.put(key, value);
        }
        return result;
    });
}
Does answered 2/7, 2020 at 9:1 Comment(0)
A
1

Just wrap the nullable value as an Optional. Pretty elegant workaround.

    listOfValues.stream()
    .collect(Collectors.toMap(e -> e.getKey(), e -> 
    Optional.ofNullable(e.getValue())))
Antalya answered 8/2, 2023 at 9:1 Comment(1)
then the value is Optional when it should be nullOverbalance
T
0

This behavior is documented in the JDK Bug System as JDK-8148463: Collectors.toMap fails on null values.

A comment on the issue written on 2023-10-10 addresses your question about whether "there a good reason why values cannot be null." It indicates that this is intentional behavior for consistency, but that the behavior needs to be better documented:

The current Collectors.toMap() code was updated by JDK-8040892, though the previous null-rejecting behavior was the same, I think. However, the null handling issue was discussed in its review thread:
https://mail.openjdk.org/pipermail/core-libs-dev/2014-April/026578.html

The conclusion was that null hostility provided the most consistent behavior across different maps and different Collectors.

Seems like the only thing to do is to clarify the specification here.

Theomorphic answered 18/1 at 5:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.