Scenario: Write Trigger on Contact to prevent inserting or updating duplicate Contacts. Use Email field to compare with existing Contacts record.
ContactTrigger
trigger ContactTrigger on Contact (before insert, before update) {
if(trigger.isBefore && trigger.isInsert || trigger.isUpdate) {
ContactTriggerHandler.checkDuplicateEmail(trigger.new);
}
} ContactTriggerHandler Approach 1
public class ContactTriggerHandler {
public static void checkDuplicateEmail(List<Contact> conLst) {
// What we have handled - allow to insert contact with null/empty email
Set<String> setEmail = new Set<String>();
Map<String,Id> mapExistingEmailtoCon = new Map<String,Id>();
for(Contact con : conLst){
if(con.Email != null || con.Email != ''){
setEmail.add(con.Email.toLowerCase());
}
}
for(Contact cont : [Select Id , Email from Contact where Email In: setEmail]){
mapExistingEmailtoCon.put(cont.Email.toLowerCase(), cont.Id);
}
System.debug(mapExistingEmailtoCon);
for(Contact con : conLst){
if(con.Email!= null && mapExistingEmailtoCon.containsKey(con.Email.toLowerCase())){
con.addError('Contact with same email already exist');
}
}
}
} ContactTriggerHandler Approach 2
public class ContactTriggerHandler {
public static void checkDuplicateEmailOnInsert(List<Contact> conLst) {
// Approach 2 - This will hit limit
// What we have handled - allow to insert contact with null/empty email
// Problems:Here limit will be hit Solution
List<Contact> existingContacts = [Select Email From Contact];
List<String> emailsLst = new List<String>();
for(Contact con: existingContacts) {
emailsLst.add(con.Email);
}
for(Contact con: conLst) {
if(con.Email != null && con.Email != '' && emailsLst.contains(con.Email)){
con.AddError('Contact with same email already exist');
}
}
}
} How the code works
- The trigger runs before insert and before update and passes the Contacts to the handler.
- The handler collects the lowercase emails of the new Contacts in a Set.
- One query finds existing Contacts with those emails and stores them in a Map.
- Any Contact whose email is already in the Map gets an error through
addError.
Problems in this version
- The null check is wrong.
con.Email != null || con.Email != ''is always true, so a Contact with no email causes a null pointer error ontoLowerCase(). - Updates block themselves. When you edit an existing Contact, the query finds that same Contact, so you cannot save it even when the email did not change.
- Duplicates inside one batch are not found. Two new Contacts with the same email in one import both pass.
Improved handler
public class ContactTriggerHandler {
public static void checkDuplicateEmail(List<Contact> conLst) {
Set<String> emails = new Set<String>();
for (Contact con : conLst) {
if (String.isNotBlank(con.Email)) {
emails.add(con.Email.toLowerCase());
}
}
if (emails.isEmpty()) {
return;
}
Map<String, Id> existing = new Map<String, Id>();
for (Contact c : [SELECT Id, Email FROM Contact WHERE Email IN :emails]) {
existing.put(c.Email.toLowerCase(), c.Id);
}
Set<String> seen = new Set<String>();
for (Contact con : conLst) {
if (String.isBlank(con.Email)) {
continue;
}
String key = con.Email.toLowerCase();
Id existingId = existing.get(key);
if ((existingId != null && existingId != con.Id) || seen.contains(key)) {
con.addError('Contact with same email already exists.');
}
seen.add(key);
}
}
} trigger ContactTrigger on Contact (before insert, before update) {
ContactTriggerHandler.checkDuplicateEmail(Trigger.new);
} How to test
- Create a Contact with an email. Create a second one with the same email in capital letters. The second should fail.
- Edit the first Contact without changing the email. It should save.
- Create a Contact with no email. It should save.
- Import two Contacts with the same new email in one file. Both rows with the duplicate should fail.
FAQ
Could duplicate rules do this?
Yes. A matching rule on Email with a duplicate rule blocks duplicates without code and gives users a clear message.
Why lowercase the email?
It makes the comparison the same for upper and lower case letters.