How to handle NullPointerException in Collectors.toMap when a mapped value is null

less than 1 minute read

In Java 8, Collectors.toMap throws NullPointerException when one of the mapped value is null. This is a known bug in OpenJDK. There are many workaround to handle this.

The ProblemPermalink

The problem statement is simple, I have a list of Employee, given that employee’s salary can be null, I want to map of salary by employeeId.

public class CollectorIssue {
public static void main(String[] args) {
Employee employee1 = new Employee(1, 1000);
Employee employee2 = new Employee(2, 2000);
Employee employee3 = new Employee(3, null);
final List<Employee> employees = new ArrayList<>();
employees.add(employee1);
employees.add(employee2);
employees.add(employee3);
final Map<Integer, Integer> employeeSalaryMap = employees.stream()
.collect(Collectors.toMap(Employee::getEmployeeId, Employee::getSalary));
System.out.println(employeeSalaryMap.size());
}
@Data
static class Employee {
private final Integer employeeId;
private final Integer salary;
}
}

When we run this code, we get a null pointer exception.

Exception in thread "main" java.lang.NullPointerException
at java.util.HashMap.merge(HashMap.java:1225)
at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)
at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1384)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
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.cobalt.dap.creative.lego.payload.generator.GeoTargetGenerator.main(GeoTargetGenerator.java:148)

Solution - Using Custom CollectorPermalink

We are going to create a custom collector, if you have no idea how to create one, follow this post

static class CustomCollector {
public static <T, K, V> Collector<T, Map<K, V>, Map<K, V>> toMap(final Function<? super T, K> keyMapper, final Function<T, V> valueMapper) {
return Collector.of(
HashMap::new,
(kvMap, t) -> {
kvMap.put(keyMapper.apply(t), valueMapper.apply(t));
},
(kvMap, kvMap2) -> {
kvMap.putAll(kvMap2);
return kvMap;
},
Function.identity(),
Collector.Characteristics.IDENTITY_FINISH);
}
}

So here is our modified code, we have just replaced Collectors implementation with CustomCollector.

public class CollectorIssueSolved {
public static void main(String[] args) {
Employee employee1 = new Employee(1, 1000);
Employee employee2 = new Employee(2, 2000);
Employee employee3 = new Employee(3, null);
final List<Employee> employees = new ArrayList<>();
employees.add(employee1);
employees.add(employee2);
employees.add(employee3);
final Map<Integer, Integer> employeeSalaryMap = employees.stream()
.collect(CustomCollector.toMap(Employee::getEmployeeId, Employee::getSalary));
System.out.println(employeeSalaryMap.size());
}
@Data
static class Employee {
private final Integer employeeId;
private final Integer salary;
}
static class CustomCollector {
public static <T, K, V> Collector<T, Map<K, V>, Map<K, V>> toMap(final Function<? super T, K> keyMapper, final Function<T, V> valueMapper) {
return Collector.of(
HashMap::new,
(kvMap, t) -> {
kvMap.put(keyMapper.apply(t), valueMapper.apply(t));
},
(kvMap, kvMap2) -> {
kvMap.putAll(kvMap2);
return kvMap;
},
Function.identity(),
Collector.Characteristics.IDENTITY_FINISH);
}
}
}

We have fixed the issue with Collectors.toMap with our custom implementation.

If you liked this article, you can buy me a coffee

Categories: ,

Updated:

Kumar Rohit
WRITTEN BY

Kumar Rohit

I like long drives, bike trip & good food. I have passion for coding, especially for Clean-Code.

Leave a comment