Optimize Apex Triggers
How to optimize Apex Triggers?
1. Refactoring 2. Checking entry criteria 3. Moving to asynchronous tools 4. Using frameworks
Do you struggle with poor performance during Trigger Execution? Is your code hitting any of the platform limits? You can improve it by using one of the following strategies:
1. Refactoring 2. Checking entry criteria 3. Moving to asynchronous tools 4. Using frameworks
Refactor
The first step that we can take to optimize triggers is to check if our code can be improved. You should start by bulkifying all methods. Next, identify unnecessary code executions, duplicates or other simple mistakes.
public class AccountTriggerHandler {
public static void beforeInsert(List<Account> newAccounts) {
for ( Account account: newAccounts ) {
try {
AccountUtils.checkForCompetition(account);
} catch (Exception e) {}
}
}
}
public class AccountUtils {
public static void checkForCompetition(Account account) {
for(Lead lead: [SELECT Name FROM Lead WHERE Name LIKE account.Name]) {
...
}
}
}Delegating parts of the logic to external classes is generally considered good practice. However, it can also be risky. While the previous example is an exaggeration, such delegation can lead to issues like SOQL/DML operations inside loops or nested loops, which can negatively impact trigger performance.
Refactor
All trigger methods should process more than one record at the time.
public class AccountTriggerHandler {
public static void beforeInsert(List<Account> newAccounts) {
List<Account> checkForCompetition = new List<Account>();
for ( Account account: newAccounts ) {
if(account.RecordType.Name == 'Partner'){
checkForCompetition.add(account);
}
}
AccountUtils.checkForCompetition(checkForCompetition);
}
}
public class AccountUtils {
public static void checkForCompetition(List<Account> accounts) {
...
}
}Optimize entry criteria
The next important step is adding or improving entry criteria. It is applicable for both, trigger records and all SOQL queries you make.
public class AccountTriggerHandler {
public static void beforeInsert(List<Account> newAccounts) {
for ( Account account: newAccounts ) {
if(account.RecordType = 'Partner') {
AccountUtils.checkForCompetition(account);
}
}
}
}It is important to ensure that your code only executes for the necessary set of records, instead of the entire data set. The same goes for queried records, you should add a filter to make sure only needed data is returned.
Escape with asynchronous
Another way is to move parts of the logic to asynchronous execution. Every action that is complicated, or when we don’t need instant results, can be moved out of the original trigger transaction.
public class AccountTriggerHandler {
public static void afterUpdate(List<Account> newAccounts, Map<Id, Account> oldAccounts) {
List<Id> emailChanged = new List<Id>();
for ( Account account: newAccounts ) {
if(account.Email != oldAccounts.get(account.Id).Email){
updateStakeholder.add(account.Id);
}
}
AccountUtils.updateStakeholder(emailChanged);
}
}
public class AccountUtils {
@Future
public static void updateStakeholder(List<Id> accounts) {
...
}
}Use Enterprise Patterns
The last piece that you can use to improve the situation is to use some frameworks that implement enterprise patterns. In case of triggers, you can use Unit of Work.
public class AccountTriggerHandler {
public static void afterUpdate(List<Account> newAccounts, Map<Id, Account> oldAccounts) {
...
uow.registerDirty(contact);
...
uow.registerDirty(case);
...
uow.commitWork();
}
}public class AccountTriggerHandler {
public static void afterUpdate(List<Account> newAccounts, Map<Id, Account> oldAccounts) {
...
uow.registerDirty(contact);
...
uow.registerDirty(case);
...
uow.commitWork();
}
}

