Title: Best Practices for Writing Apex Triggers: A Guide for Developers
Introduction:
Apex Triggers are powerful tools that allow developers to execute custom logic before or after certain operations, such as insert, update, delete, and undelete in Salesforce. While triggers are immensely useful, writing them efficiently and following best practices can be tricky, especially for developers new to Apex.
In this guide, we’ll cover the best practices for writing Apex triggers that are scalable, maintainable, and follow Salesforce’s best practices. Whether you’re a beginner or an experienced developer, these tips will help you write effective triggers that adhere to best coding standards.
Section 1: One Trigger per Object
The most important best practice in Apex development is to limit the number of triggers per object to just one. This ensures that all logic related to the object is centralized, making it easier to manage and debug.
- Why?: Multiple triggers on the same object can cause issues like order of execution problems and conflicts in data updates.
- How?: Use a Trigger Handler pattern, where a single trigger calls separate Apex classes (handlers) to perform various operations.
Section 2: Bulkify Your Triggers
One of the cardinal rules of writing Apex triggers is to always bulkify your code. This means making sure your trigger can handle multiple records efficiently in a single execution.
- Why?: Salesforce can process up to 200 records at a time in a single transaction. Writing triggers that only handle one record at a time will result in errors and poor performance.
- How?: Use for loops to iterate over collections (like
Trigger.new
andTrigger.old
) instead of processing each record individually. For example:
for (Account acc : Trigger.new) {
// Process each account in the list
}
Section 3: Avoid SOQL and DML in Loops
One of the most common mistakes in Apex triggers is putting SOQL queries or DML operations inside loops. This can lead to hitting Salesforce governor limits, such as the 100 SOQL queries per transaction or 150 DML statements per transaction.
- Why?: Executing queries or DML operations inside a loop can cause serious performance issues and governor limit errors.
- How?: Always move SOQL queries and DML operations outside of loops. Instead, use collections (like sets or lists) to gather data, then perform bulk operations outside of the loop.
For example:
List<Contact> contactsToUpdate = new List<Contact>();
for (Account acc : Trigger.new) {
if (acc.SomeField == 'Some Value') {
contactsToUpdate.add(new Contact(AccountId = acc.Id, SomeField = 'Updated Value'));
}
}
// Perform DML outside of the loop
update contactsToUpdate;
Section 4: Use Context Variables Wisely
Salesforce provides several context variables to help you access the state of records during trigger execution, like Trigger.new
, Trigger.old
, Trigger.isInsert
, Trigger.isUpdate
, etc. Understanding and using these variables correctly is crucial to writing good triggers.
- Why?: Context variables allow you to manage logic based on the trigger event (e.g., insert, update, delete).
- How?: Use
Trigger.new
to access the new version of records andTrigger.old
to access the old version of records. Also, make sure to useTrigger.isInsert
,Trigger.isUpdate
, etc., to control when your logic should be applied.
Example:
if (Trigger.isUpdate) {
for (Account acc : Trigger.new) {
Account oldAcc = Trigger.oldMap.get(acc.Id);
if (acc.Name != oldAcc.Name) {
// Logic for name change
}
}
}
Section 5: Utilize Trigger Handlers
Following the single trigger per object principle, it’s good practice to offload complex logic to handler classes. A Trigger Handler pattern separates trigger logic from the trigger itself, making the code cleaner, easier to maintain, and reusable.
- Why?: This reduces the complexity of triggers and makes the code modular and easier to test.
- How?: Create an Apex class that contains the business logic and call that class from the trigger. This keeps your trigger small and focused on when to execute logic, not how.
Example:
trigger AccountTrigger on Account (before insert, before update) {
AccountTriggerHandler.handleBeforeInsert(Trigger.new);
AccountTriggerHandler.handleBeforeUpdate(Trigger.oldMap, Trigger.new);
}
In the handler class:
public class AccountTriggerHandler {
public static void handleBeforeInsert(List<Account> newAccounts) {
// Logic for before insert
}
public static void handleBeforeUpdate(Map<Id, Account> oldAccounts, List<Account> newAccounts) {
// Logic for before update
}
}
Section 6: Write Test Classes for Your Triggers
Writing robust test classes for your triggers is essential, especially to ensure that your triggers handle bulk operations, edge cases, and governor limits properly.
- Why?: Salesforce requires at least 75% code coverage for deployment, and tests help ensure that your triggers are error-free and follow best practices.
- How?: Write test classes that cover positive scenarios (happy paths), as well as negative and bulk data handling scenarios. Make sure to use the
Test.startTest()
andTest.stopTest()
methods for performance testing.
Example of a test class:
@isTest
public class AccountTriggerTest {
@isTest
static void testAccountInsert() {
List<Account> accounts = new List<Account>();
for (Integer i = 0; i < 200; i++) {
accounts.add(new Account(Name='Test Account ' + i));
}
Test.startTest();
insert accounts;
Test.stopTest();
// Assert logic
}
}
Section 7: Handle Recursion in Triggers
Recursion in triggers can lead to unwanted re-execution, causing governor limits to be hit. To prevent this, it’s important to track whether your trigger logic has already executed.
- Why?: Triggers can sometimes re-execute multiple times in the same transaction due to DML operations.
- How?: Use static variables to prevent recursion in triggers. For example:
apexCopy codepublic class TriggerHelper {
public static Boolean isTriggerExecuted = false;
}
In your trigger:
if (!TriggerHelper.isTriggerExecuted) {
TriggerHelper.isTriggerExecuted = true;
// Trigger logic here
}
Conclusion:
Apex triggers are a key part of Salesforce development, but they need to be written with best practices in mind to ensure scalability and performance. By following these practices—like bulkifying your triggers, using trigger handlers, avoiding SOQL/DML in loops, and writing robust test classes—you can ensure your triggers are efficient and maintainable.
Remember, a well-written trigger can be the difference between a smooth-running Salesforce org and one that constantly hits governor limits. Happy coding!
Call-to-Action:
Want to stay updated on more Salesforce development tips and tricks? Subscribe to my blog for the latest articles on Apex, Lightning Web Components, and Salesforce best practices.