🔴 The Error You're Seeing

Confirm this matches your console output. If it does, you're in the right place.

ERROR LOGjava.lang.IllegalStateException: Duplicate key k (attempted merging values 1 and 2) at java.base/java.util.stream.Collectors.duplicateKeyException(Collectors.java:135) at java.base/java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$0(Collectors.java:182) at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)

⚡ Quick Fix Works 80% of the time

Supply a merge function as the third argument to toMap.

Map<Integer, String> m = rows.stream() .collect(Collectors.toMap(Row::id, Row::name, (a, b) -> b));

🧠 Why this Happens

Tap to expand the deep technical explanation

toMap uses uniqKeysMapAccumulator: putIfAbsent(key, value), then a null-check on any previous mapping. A second arrival with an equal key finds non-null previous and calls duplicateKeyException, reporting old and new values. This is deliberate design: keeping first-or-last silently would hide data-quality bugs, so the JDK forces you to state a merge policy explicitly.

The HITEC City Parking Spot Analogy:

Two employees named Raj clock into a locker system with one slot per key: it halts and shows both time cards instead of guessing whose shift counts.

🔁 How to Reproduce Confirm this is your error

Stream.of(1, 2).collect( Collectors.toMap(x -> "k", x -> x)); // duplicate key "k"

🛠️ Solutions (5 Ways to Fix)

Solution 1✓ Most common cause

Add a merge function: (a, b) -> a or b

👉 Use this if duplicates are expected data and a deterministic winner exists.

The third argument decides collisions: keep-first, keep-latest, min/max by comparator - any BinaryOperator works.

Collectors.toMap(User::id, User::name, (existing, replacement) -> existing)
Solution 2

Merge semantically: Integer::sum

👉 Use this if duplicates should combine rather than compete, e.g. quantity per product.

Merge functions are full reducers: summing counts, concatenating strings, or max-ing timestamps expresses intent instead of hiding it.

Collectors.toMap(Sale::sku, Sale::qty, Integer::sum)
Solution 3

groupingBy when multiples belong together

👉 Use this if repeated keys mean you actually wanted a Map<K, List<V>>.

groupingBy collects every value per key downstream, eliminating the collision conceptually instead of arbitrating it.

import static java.util.stream.Collectors.groupingBy; Map<Integer, List<Order>> byCust = orders.stream() .collect(groupingBy(Order::customerId));
Solution 4

Preserve order with a LinkedHashMap supplier

👉 Use this if the merged map must keep encounter order via the four-arg toMap overload.

The fourth argument supplies the map implementation; LinkedHashMap keeps insertion order while the merge function handles collisions.

Collectors.toMap(k, v, (a, b) -> b, LinkedHashMap::new)
Solution 5

De-duplicate upstream before collecting

👉 Use this if duplicates indicate dirty source data worth surfacing early.

Filter to one record per key so toMap receives unique keys; the exception doubles as an integrity alarm when uniqueness was assumed.

Set<Integer> seen = new HashSet<>(); list.stream() .filter(u -> seen.add(u.id())) .collect(Collectors.toMap(User::id, u -> u));

📋 Version Notes

Java 8

Message was just "Duplicate key k" - key only, no values.

Java 9+

Message extended with "(attempted merging values X and Y)" naming both colliding values; behavior otherwise unchanged through Java 21+.

🛡️ How to Prevent This Next Time

Never write a two-argument toMap against data you do not control. Decide first: merge, group, or guarantee uniqueness upstream.