Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java toMap: reject or merge duplicate keys deliberately

Last updated: 5 Oct 20264 min read
tutorial
IntermediateBy AITrove Editorial

Collectors.toMap accumulates stream elements into a map, and a duplicate key needs either an explicit merge rule or a visible failure.

Download Java source kit

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

Java
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

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.

java
collector-key-collisions
Storage details