Introduction
Salesforce triggers are a powerful feature – that allows developers to execute custom logic before or after specific database events like – Insert, Update, Delete or Undelete.
However, one of the common challenges developers face when working with triggers is Recursion.Â
In this blog post, we will discuss how trigger recursion happens, why it is problematic and what are the best practises that developers should follow to prevent it.
What is Recursion in Triggers?
Recursion in triggers occurs when a trigger calls itself repeatedly and executes multiple times.
For Example: If you update a record in an After Update trigger, it will fire the same update event again and again, potentially creating an infinite loop.
Real Time Example
You get a requirement that – Whenever any field on an Account record is updated, update the Account Status field to “Processed”.
For the above requirement, developer writes the below code:
trigger AccountTrigger on Account (after update) {
for (Account acc : Trigger.new) {
acc.Status__c = 'Processed'; // Update the Status field
update acc; // This update causes the trigger to fire again
}
}
This code above will cause recursion because –Â
- When a user updates an Account record, the trigger detects the update and sets the Account’s status to Processed.
- Now when the trigger has updated the Account’s Status to Processed, this update causes the same trigger to fire again and Account’s status to be updated again.
- The trigger runs agin, repeating the update, and the process keeps going, causing an endless loop.
Why is Recursion Problematic?
Recursion in Salesforce triggers can cause several issues:
- Governor Limit Violations:
Recursion can result in excessive SOQL queries, DML operations, and CPU time, leading to governor limits being exceeded. - Unintended Data Modifications:
Recursion might cause records to be updated multiple times unintentionally, leading to incorrect or unexpected data in your Salesforce org. - Poor Performance:
Continuous trigger execution can slow down your system and affect the performance of other processes.
How to Stop Recursion in Triggers?
Here are some best practises and techniques to prevent trigger recursion
1. Use Static Boolean Variable
This is a common approach to stop recursion is using Static variable in an Apex class.
Static variables persist only for the duration of a single transaction and can be used to track whether the trigger logic has already been executed or not.
Once static variable is set, you can use it to ensure that the trigger does not execute its logic multiple times for the same record.
Example: Using Static Variables to Prevent Recursion
trigger AccountTrigger on Account (before update) {
AccountTriggerHandler. preventRecursiveTrigger (Trigger.new);
}
public class AccountTriggerHandler {
// Declare a static boolean to track trigger execution
public static Boolean isTriggerExecuted = false;
public static void preventRecursiveTrigger (List accounts) {
// Check if the trigger has already been executed
if (!isTriggerExecuted) {
isTriggerExecuted = true;
// Trigger logic here
for (Account acc : accounts) {
acc.Description = 'Updated by trigger';
}
update accounts; // Be cautious when updating in a trigger
} else {
System.debug('Trigger recursion prevented');
}
}
}
In this example – the isTriggerExecuted static variable prevents the trigger logic from running more than once in a single transaction.
Once the trigger logic runs, the variable is set to True. This ensures that the logic is not executed again within the same transaction.
Why this Static Variable works?
Static variables are shared across trigger executions within the same transaction, so they act as a flag to track whether the trigger has already run or not.
Disadvantages of using Static Boolean Variable
Using static boolean variable is good for less than 200 records. But what if – we are inserting 1000 Account records, and we want our trigger on Account object to run on all 1000 records.
In this scenario, using static boolean variable is not recommended as it will update first 200+ records and then will skip the others.
2. Using SET to track record ids
This is one of the best way to track recursion at record level.Â
In this approach, developers create a static set or map to store all executed record ids, so that if the trigger goes in recursive mode, then it scans the Set and checks if the same record is already executed or not.
This process ensures that – the records that have been processed earlier are skipped.
Example: Using a Set to Prevent Recursion for Specific Records
trigger AccountTrigger on Account (before update) {
AccountTriggerHandler. handleAccountUpdates (Trigger.new);
}
public class AccountTriggerHandler {
public static Set processedAccountIds = new Set();
public static void handleAccountUpdates (List accounts) {
List accountsToUpdate = new List();
for (Account acc : accounts) {
if (!processedAccountIds. contains(acc.Id)) {
processedAccountIds. add(acc.Id);
acc.Description = 'Processed by trigger';
accountsToUpdate.add(acc);
}
}
if (!accountsToUpdate. isEmpty()) {
update accountsToUpdate;
}
}
}
In this case, we use a Set<Id> to track which account records have been processed. If a record has already been processed (i.e., it’s in the set), the logic will skip that record, thus preventing recursion.
Why this works?
By tracking record IDs, you ensure that each record is processed only once within a single transaction, preventing infinite loops or unnecessary reprocessing.
3. Use a Recursive Flag on the Record Itself
This is another option – to add a custom field (like a checkbox) to the object that will indicate whether the record has been already processed by trigger or not.
This method ensures that trigger logic only runs once for each record, even across multiple transactions.
Example: Using a Custom Field to Prevent Recursion
- Create a custom checkbox field:
Let say – you are writing trigger on Account object. So, here create a custom checkbox field on Account object called Trigger_processed__c. - Below is the trigger code for Account object.
trigger AccountTrigger on Account (before update) {
for (Account acc : Trigger.new) {
if (acc.Trigger_Processed__c == false) {
acc.Trigger_Processed__c = true; // Mark the record as processed
acc.Description = 'Updated by trigger';
}
}
}
This approach uses a field on the record to indicate whether the trigger logic has already been applied to that record or not.
Once the field is set to true, the trigger logic won’t be executed for that record again, which will stop recursion.
Why this works?
The custom checkbox field on an object allows a developer to control trigger execution across multiple transactions.
This becomes very useful if recursion might occur not just within a single transaction but across multiple trigger execution.
4. Use Conditional Logic to Limit Trigger Actions
Using conditional logic is also one of the way to prevent recursion. However, this approach might not be suitable for all scenarios, but its always better to know.
Example: Using Conditional Logic
trigger AccountTrigger on Account (before update) {
for (Account acc : Trigger.new) {
// Only execute logic if the Description field is empty
if (String.isBlank (acc.Description)) {
acc.Description = 'Updated by trigger';
}
}
}
In this example, the trigger only executes if Description field is blank, thus limiting the chances of recursion when updating the same record.
Why this works?
By adding specific condition for trigger execution, developers can minimise the likelihood of recursion and ensure that the trigger logic is applied only when it is necessary.
Conclusion
Trigger recursion can lead to performance issues, data integrity problems, and governor limit violations, but it’s easily preventable with proper techniques.
Using static variables, record-level tracking with sets, custom flags on records, or conditional logic are all effective ways to stop recursion and ensure that your triggers run efficiently and correctly.