Skip repeated addresses in contact imports

Java · Spring Boot · beginner · modification

Imports of a file that repeats an address failed on the unique index on `(list_id, email)`, so the whole upload was rejected. This dedupes the rows before writing: each email is normalised with `strip().toLowerCase(Locale.ROOT)`, the first occurrence is kept in file order, and repeats are counted and returned as `duplicatesSkipped`.

Mailing-list backend on Spring Boot 3.3 (Java 21) with PostgreSQL 16. `ImportController` (existing, unchanged) handles `POST /api/lists/{listId}/imports`: it creates the new list, parses the CSV (rejecting blank rows and files over 200,000 rows), calls `importEmails` and returns the `ImportResult` as JSON; nothing else constructs an `ImportResult`. Every import targets a freshly created list, so no contacts exist in it beforehand. `ContactWriter.insertAll(listId, emails)` (existing, unchanged) writes the given emails with JDBC batch inserts of 1,000 rows in one transaction; `contacts` has a unique index on `(list_id, email)`. Customers upload exports from other mailing tools; files of 100,000 to 200,000 rows are common, and repeats are typically a few percent of rows.

Requirements

Files touched

--- src/main/java/com/acme/mailing/imports/ContactImportService.java
 
     public ImportResult importEmails(long listId, List<String> emails) {
-        List<String> normalized = new ArrayList<>(emails.size());
+        List<String> unique = new ArrayList<>(emails.size());
+        int duplicates = 0;
         for (String email : emails) {
-            normalized.add(normalize(email));
+            String normalized = normalize(email);
+            if (unique.contains(normalized)) {
+                duplicates++;
+            } else {
+                unique.add(normalized);
+            }
         }
-        writer.insertAll(listId, normalized);
-        log.info("Imported {} contacts into list {}", normalized.size(), listId);
-        return new ImportResult(normalized.size());
+        writer.insertAll(listId, unique);
+        log.info("Imported {} contacts into list {}, skipped {} duplicate rows", unique.size(), listId, duplicates);
+        return new ImportResult(unique.size(), duplicates);
     }
 
--- src/main/java/com/acme/mailing/imports/ImportResult.java
 package com.acme.mailing.imports;
 
-public record ImportResult(int imported) {
+public record ImportResult(int imported, int duplicatesSkipped) {
 }
 

Review this PR

Java practice