How to avoid recursion in Trigger

In this post, we will learn about what is a recursive trigger and how to avoid the recursion in a Trigger. Many Developers face recursive triggers issues. For example in the ‘after update’ trigger, the Developer is performing an update operation, and this lead to the recursive call.

What is a recursive Trigger?

Recursion is the process of executing the same Trigger multiple times to update the record again and again due to automation. There may be chances we might hit the Salesforce Governor Limit due to Recursive Trigger.

Recursive trigger example

The below example shows what is a Recursive Trigger and how it can be avoided.

How to avoid recursion in trigger?

There are different ways to solve the recursion in the trigger.

  1. Use Static Boolean Variable
  2. Use Static Set to Store Record Id.
  3. Use Static Map
  4. Use Old Map
  5. Follow Best practice for triggers

Let’s take a look in detail at all solutions and their limitations.

1. Use Static Boolean Variable

You can create a class with a static Boolean variable with a default value of true. In the trigger, before executing your code keep a check that the variable is true or not. Once you check make the variable false.

Apex Class with Static Variable

public class ContactTriggerHandler{
     public static Boolean isFirstTime = true;
}

Trigger Code

Trigger ContactTriggers on Contact (after update){
    Set<String> accIdSet = new Set<String>(); 
    if(ContactTriggerHandler.isFirstTime){
        ContactTriggerHandler.isFirstTime = false;
        System.debug('---- Trigger run ---->'+Trigger.New.size() );
         for(Contact conObj : Trigger.New){
            if(conObj.name != 'Test') {
                accIdSet.add(conObj.accountId);
            }
         }
       // any code here
    }
}

This is good for less than 200 records. But what about if we have a large set of records it won’t work. It will update the first 200+ records then it will skip the others. Let’s execute the above code with more than 200+ records.

List<Contact> listCont =[Select id, firstname from contact limit 400];
update listCont;
System.debug('-listCont.size--->'+listCont.size());

In my org, we have 327 contacts. Let’s query them all and update them. In this case, the trigger should execute 2 times. But due to static variables trigger will only execute once and will skip the remaining record from processing.

Use Static Boolean Variable to avoid recursion in Trigger

Check Salesforce document for Apex Trigger Best practices to avoid recursion by using Static Boolean variable in apex class. Static Boolean is an anti-pattern so please DO NOT USE.

2. Use Static Set or Map

So It is better to use a Static set or map to store all executed record ids. So when next time it will execute again we can check record is already executed or not. Let modify the above code with the help of Set.

public class ContactTriggerHandler{
     public static Set<Id> setExecutedRecord = new Set<Id>();
}

Let us the same set variable in Trigger.

trigger ContactTriggers on Contact (after update){
        System.debug('---- Trigger run ---->'+Trigger.New.size() );
         for(Contact conObj : Trigger.New){
             if(ContactTriggerHandler.setExecutedRecord.contains(conObj.id)){             
                if(conObj.name != 'Test') {
                    // logic
                }
            }    
         }
       // any code here
}

Let’s test the above code with more than 200 records. Execute the same script and see the result.

Use Static Set to avoid recursion in Trigger
Use Static Set to avoid recursion in Trigger

The use of a static Set is less than ideal because there are plenty of times when you may get both an insert and an update within the same transaction (esp. with a mix of the apex and flow logic).

3. Use Static Map

You can also use Static map to store the event name and set of Id like below.

public static Map<String,Set<Id>> mapExecutedRecord = new Map<String,Set<Id>>;

Use it like below

 if(!ContactTriggerHandler.mapExecutedRecord.containsKey('afterUpdate')){
 ContactTriggerHandler.mapExecutedRecord.put('afterUpdate',new Set<ID>());
 }

....
for(){
  if(ContactTriggerHandler.mapExecutedRecord.get('afterUpdate').contains(conObj.id)) {
  }
}

In this way you can handle all event using same map. Check out this thread.

4. Use Old Map

Always use Old map to compare the values before executing the trigger logic. Here is example how we can compare old value to resolve the recursion in Trigger.

for(Opportunity opp:Trigger.New){
	if(opp.Stage != Trigger.oldMap.get(opp.Id).Stage){
		//Your logic
	}
}

In case of OldMap your code will only execute once value will change.

5. Follow Best practice for triggers

Follow Trigger best practices and use the Trigger framework to resolve the recursion.

  1. One trigger per object so you don’t have to think about the execution order as there is no control over which trigger would be executed first.
  2. Logic-less Triggers – use Helper classes to handle logic.
  3. Code coverage 100%
  4. Handle recursion – To avoid the recursion on a trigger, make sure your trigger is getting executed only one time. You may encounter the error : ‘Maximum trigger depth exceeded’, if recursion is not handled well.

Use Apex Code best practices in Salesforce to design the better Trigger.

Summary

There are different ways to handle recursive triggers in triggers. But I highly recommend using Trigger Framework with a static set to avoid the recursion in the trigger.

Amit Chaudhary
Amit Chaudhary

Amit Chaudhary is Salesforce Application & System Architect and working on Salesforce Platform since 2010. He is Salesforce MVP since 2017 and have 17 Salesforce Certificates.

He is a active blogger and founder of Apex Hours.

Articles: 461

10 Comments

  1. All of your examples forget about the possibility of a partial update being blocked because of a retry. You should always set the flag or Set values when starting a trigger, then reset/remove those values at the end of the trigger. Even better, using a technique like this, you can be more selective with your recursion blocking.

    See my post: https://salesforce.stackexchange.com/a/296817

  2. In the Set Logic

    if(ContactTriggerHandler.setExecutedRecord.contains(conObj.id)){
    if(conObj.name != ‘Test’) {
    // logic
    }
    }

    I think there should be a not check

    if(!ContactTriggerHandler.setExecutedRecord.contains(conObj.id)){
    if(conObj.name != ‘Test’) {
    // logic
    }
    }

  3. I don’t see why you would recommend anything other than using the oldMap, unless you’re executing code without a conditional check (which is rare, in my experience). It’s built-in, so we don’t have to worry about maintaining a static Set or Map. If you want a specific block of code to run every time a value is changed from X to Y, then it doesn’t matter how many recursive entries there are into the trigger; it will always skip unless that condition is true, and then you would want it to run anyway.

  4. Shouldn’t in the second approach we check for id’s that are not added in the set, run the logic and add that id to the set!
    Like:
    if(!ContactTriggerHandler.setExecutedRecord.contains(conObj.id)){
    setExecutedRecord.add(conObj.id);
    //logic
    }

  5. Please rectify with a ‘!’ in the second approach if(!ContactTriggerHandler.setExecutedRecord.contains(conObj.id)){

    Future methods can also be considered, Use of System.Isfuture and System.IsBatch can also be considered for some of the use cases.

  6. Please rectify with a ‘!’ in the second approach if(!ContactTriggerHandler.setExecutedRecord.contains(conObj.id)){

    Future methods can also be considered, Use of System.Isfuture and System.IsBatch can also be considered for some of the use cases.

Leave a Reply

Your email address will not be published. Required fields are marked *