Collectors.toMap accumulates stream elements into a map, and a duplicate key needs either an explicit merge rule or a visible failure.
Java toMap: reject or merge duplicate keys deliberately
The complete program targets Java 8. Compile it as one source file; its output is checked against the lesson.
Choose what duplicate means
Two receipt rows share a currency code. If the target represents total exposure per currency, summing amounts is appropriate. If the target represents one authoritative payment per identifier, keeping the first row would conceal inconsistent input. The merge function belongs to the domain contract.
The program first verifies that the two-argument collector rejects a duplicate, then supplies a sum rule and a TreeMap factory for reproducible key order. It uses checked integer addition so overflow is rejected rather than wrapping silently.
Parallel reduction adds another constraint
A merge function used in parallel reduction must produce the intended result under grouping changes. Subtracting values in the merge function is not a valid substitute for adding them. Side effects on another map or counter also introduce ordering and concurrency dependencies.
The fixture contains positive bounded amounts. Real currency calculations need a documented unit and rounding rule, often BigDecimal or a checked integral minor-unit value. A collection operation does not validate currency scale or convert between currencies.
Working program
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
public class CurrencyExposure {
static class Row {
final String currency; final int minorUnits;
Row(String currency, int minorUnits) { this.currency = currency; this.minorUnits = minorUnits; }
}
public static void main(String[] args) {
List<Row> rows = Arrays.asList(new Row("INR", 120), new Row("INR", 80), new Row("USD", 50));
try { rows.stream().collect(Collectors.toMap(row -> row.currency, row -> row.minorUnits)); }
catch (IllegalStateException rejected) { System.out.println("duplicate rejected"); }
Map<String,Integer> totals = rows.stream().collect(Collectors.toMap(
row -> row.currency, row -> row.minorUnits, Math::addExact, TreeMap::new));
System.out.println(totals);
}
}Output
duplicate rejected
{INR=200, USD=50}Costs and boundaries
TreeMap insertion costs O(log k) for k distinct keys, so this collector uses O(n log k) time and O(k) map storage. The ordinary hash-map form has a different cost profile. Sorting is chosen for deterministic output, not claimed as a faster collector.
Common Mistakes
- Do not silently overwrite an identifier that should be unique.
- Make the merge function suitable for the reduction grouping.
- Define money units before summing.
Read next
Java streams: lazy pipelines and bounded results, Java HashMap: keys, collisions, and update operations, Java BigDecimal: decimal amounts and explicit rounding.
