How to Block Recursion in a Generic Trigger Handler?

Question
When implementing a generic trigger handler that processes all TriggerOperation
types and supports arbitrary SObjectType
, recursion can become a major issue. Recursion occurs when a trigger causes an update that invokes the same trigger again, leading to infinite loops or redundant processing.
Consider the following example trigger:
trigger ExampleTrigger on Example__c (before insert, before update, before delete,
after insert, after update, after delete, after undelete) {
SObjectType t = Example__c.SObjectType;
MyHandler.handle(t, Trigger.operationType, Trigger.oldMap, Trigger.newMap);
}
With the corresponding handler:
public inherited sharing MyHandler {
public void handle(SObjectType t, TriggerOperation op,
Map<Id, SObject> oldMap, Map<Id, SObject> newMap) {
// Logic that may cause recursion
...
}
}
Answer
A common approach to recursion blocking is using a static Boolean flag, but this method can be insufficient in complex scenarios. Below are different strategies for handling recursion effectively.
CRS Info Solutions offers expert Salesforce online training with real-time projects, certification guidance, interview coaching, and a job-ready approach. Enroll for free demo today!!!
1. Using a Set of Trigger Operations
One way to block recursion is by maintaining a static Set
that tracks which operations have already been executed.
public inherited sharing MyHandler {
private class Kind {
private SObjectType type;
private TriggerOperation operation;
public Kind(SObjectType type, TriggerOperation operation) {
this.type = type;
this.operation = operation;
}
public Boolean equals(Object obj) {
if (obj instanceof Kind) {
Kind other = (Kind) obj;
return this.type == other.type && this.operation == other.operation;
}
return false;
}
public Integer hashCode() {
return String.valueOf(type) + String.valueOf(operation).hashCode();
}
}
private static final Set<Kind> BLOCKED = new Set<Kind>();
public void handle(SObjectType t, TriggerOperation op,
Map<Id, SObject> oldMap, Map<Id, SObject> newMap) {
if (BLOCKED.contains(new Kind(t, op))) return;
// Execute logic
BLOCKED.add(new Kind(t, TriggerOperation.BEFORE_INSERT));
BLOCKED.add(new Kind(t, TriggerOperation.AFTER_INSERT));
insert newMap.values();
}
}
This approach ensures that the same SObjectType
and TriggerOperation
combination does not execute multiple times in a single transaction. However, care must be taken to reset the BLOCKED
set appropriately to avoid blocking valid updates.
2. Using a Static Boolean Flag for Known Recursive Updates
Another approach is to use a Boolean flag but only within known recursive scenarios.
public class AccountTriggerHandler {
static Boolean isInAccountUpdate = false;
public static void afterUpdate(Account[] oldValues, Account[] newValues) {
if (!isInAccountUpdate) {
if (shouldDoUpdate()) {
isInAccountUpdate = true;
updateRecords(oldValues, newValues);
isInAccountUpdate = false;
}
}
}
}
This approach works well when recursion is limited to specific operations, but it does not handle cases where updates occur across different operations or across multiple records in bulk transactions.
3. Using a “Rising Edge” Trigger Pattern
A more robust way to prevent unnecessary recursion is to process only records that have actually changed.
public static void afterUpdate(Account[] oldValues, Account[] newValues) {
Account[] oldChanges = new Account[0], newChanges = new Account[0];
for (Integer i = 0, s = newValues.size(); i < s; i++) {
if (recordChanged(oldValues[i], newValues[i])) {
oldChanges.add(oldValues[i]);
newChanges.add(newValues[i]);
}
}
processChangedRecords(oldChanges, newChanges);
}
By filtering out records that haven’t changed, this approach naturally avoids recursion and reduces unnecessary processing.
4. Using a Static Set to Track Processed Records
If multiple records of the same type can be updated in a single transaction, using a Set<Id>
to track processed records can help avoid redundant updates while maintaining flexibility.
public class AccountTriggerHandler {
static Set<Id> accountIds = new Set<Id>();
public static void afterUpdate(Account[] oldValues, Account[] newValues, Set<Id> accountIdSet) {
if (accountIds.containsAll(accountIdSet)) {
return;
}
accountIds.addAll(accountIdSet);
doMainLogicHere();
accountIds.removeAll(accountIdSet);
}
}
This approach ensures that updates are applied only once per transaction and that the lock is cleared at the end of execution. However, failing to reset the Set
can lead to issues when dealing with workflow updates, approval processes, or unit test scenarios.
Conclusion
There is no single “magic bullet” to prevent recursion in Apex triggers, so a good strategy combines multiple techniques. Using a Set or Boolean flag helps with simple recursion prevention, while filtering actual data changes minimizes unnecessary updates. Ensuring trigger logic unlocks at the end allows valid updates, and considering transaction-wide state helps manage bulk operations. By designing trigger logic carefully, you can effectively limit recursion while maintaining reliable and efficient processing.
Salesforce Training in Chennai: Unlock Your Potential
Elevate your career with our comprehensive Salesforce training in Chennai, designed to equip you with expertise in Admin, Developer, and AI modules. Our program offers unparalleled certification guidance, rigorous interview preparation, and industry-aligned training to ensure you gain a competitive edge. With in-depth modules and expert-led sessions, you’ll build a solid foundation in Salesforce while mastering advanced techniques to meet real-world demands.
Our unique learning approach combines practical hands-on sessions with detailed class notes, enabling you to gain job-ready skills and confidence. Whether you’re starting fresh or upskilling, our training ensures you stand out in the fast-paced Salesforce ecosystem.
Take the first step toward your success by joining our free demo class today and experience the best in Salesforce education!!!
Related Posts:
Detailed Guide to Triggers in Salesforce
Trigger Framework in Salesforce