Orderinghard5-8 years

A reporting job builds `Set<Customer> sorted = new TreeSet<>(Comparator.comparing(Customer::signedUp)); sorted.addAll(customers);` to sort and deduplicate customers by id in one step. The export has 6,000 rows where the source had 10,000. Nothing threw. What went wrong, and what's the actual rule being violated?

A TreeSet decides whether an element is "already present" entirely by its comparator returning zero — it never calls equals() at all, which is easy to forget because every other collection in the framework that promises uniqueness (HashSet) uses equals() for exactly that decision. The comparator here only looks at signup date, so any two customers who signed up on the same day compare as equal, and TreeSet.add treats the second one as a duplicate of the first and silently drops it — not a bug in the set, it did exactly what it was told: "these two count as the same." Bulk signups from a single campaign put hundreds of customers on the same day, and each day of the campaign kept exactly one. The rule this breaks is called consistency with equals: a comparator used for sorting or deduplication should only return zero for objects that really are the same by the type's own definition of equality, and here the comparator was doing double duty — ordering by date, and (accidentally) declaring "same date" to mean "same customer," which was never true.

The lesson behind it →