Why does Collectors.toMap report value instead of key on Duplicate Key error?

This is really a question about a minor detail, but I'm under the impression to get something wrong here. If you add duplicate keys using Collectors.toMap-method it throws an Exception with message "duplicate key ". Why is the value reported and not the key? Or even both? This is really misleading, isn't it?

Here's a little test to demonstrate the behaviour:

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

public class TestToMap {

    public static void main(String[] args) {

        List<Something> list = Arrays.asList(
            new Something("key1", "value1"),
            new Something("key2", "value2"),
            new Something("key3", "value3a"),
            new Something("key3", "value3b"));

        Map<String, String> map = list.stream().collect(Collectors.toMap(o -> o.key, o -> o.value));
        System.out.println(map);
    }

    private static class Something {
        final String key, value;

        Something(final String key, final String value){
            this.key = key;
            this.value = value;
        }
    }
}

This is reported as a bug, see JDK-8040892, and it is fixed in Java 9. Reading the commit fixing this, the new exception message will be

String.format("Duplicate key %s (attempted merging values %s and %s)", k, u, v)

where k is the duplicate key and u and v are the two conflicting values mapped to the same key.


As the other answers already state, it’s a bug that will be fixed in Java 9. The reason, why the bug arose, is, that toMap relies on Map.merge which has the signature

V merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction)

This method will insert the key-value mapping, if there was no previous mapping for the key, otherwise, the remappingFunction will be evaluated to calculate the new value. So, if duplicate keys are not allowed, it’s straight-forward to provide a remappingFunction which will unconditionally throw an exception, and you’re done. But…if you look at the function signature, you’ll notice that this function only receives the two values to be merged, not the key.

When the throwingMerger was implemented for Java 8, it was overlooked that the first argument is not the key, but even worse, it is not straight-forward to fix.

You’ll notice, if you try to provide an alternative merger using the overloaded toMap collector. The key value simply isn’t in scope at this point. The Java 9 developers had to change the whole toMap implementation for the no-duplicate case to be able to provide an exception message reporting the affected key…


This is a know bug in Jdk 8. The message thrown should either display at least two values for which there is a key collision or ideally the key for which the collision happened. Attached is the link for the same