Why recursion happens in Apex triggers
Recursion in Apex triggers occurs when a trigger causes a DML operation that fires the same trigger (or another trigger) again. Without proper guards, this can result in infinite loops or unintended repeated processing, leading to governor limits being hit or data corruption.
Common causes
– Performing updates/inserts/deletes on the same object (or related objects) within the trigger handler.
– Calling code that performs DML which re-enters the same trigger.
– Multiple triggers and helper classes without a shared guard mechanism.
Best practices to avoid recursion in triggers
Use one or more of the following patterns to prevent recursion reliably and safely.
1) Use a static Boolean flag in a helper class (simple guard)
Static variables live for the duration of a single transaction. This is the simplest way to prevent a trigger from running its body multiple times during the same transaction.
public class TriggerGuard {
public static Boolean isExecuting = false;
}
trigger AccountTrigger on Account (before update) {
if(TriggerGuard.isExecuting) return;
try {
TriggerGuard.isExecuting = true;
// your trigger logic that might perform DML
} finally {
// optional: do not reset for the transaction lifecycle; leaving true is safe
}
}
Pros: Simple to implement. Cons: Coarse-grained — it prevents re-entry entirely across the object/trigger context for the whole transaction.
2) Use a static Set to track processed records (record-level guard)
If the trigger must process different records multiple times safely (for example, triggers calling other code that updates unrelated records), use a Set of Ids to ensure each record is processed only once.
public class TriggerState {
public static Set
}
trigger AccountTrigger on Account (before update, after update) {
Map
for(Account a : Trigger.new) {
if(!TriggerState.processedAccountIds.contains(a.Id)) {
recordsToProcess.put(a.Id, a);
}
}
if(recordsToProcess.isEmpty()) return;
// perform logic and DML only for records not yet processed
// after successful processing, add ids to processed set
TriggerState.processedAccountIds.addAll(recordsToProcess.keySet());
}
3) Use a trigger framework / handler with idempotency
Adopt a framework (or write a handler pattern) where each trigger delegates logic to a singleton handler class. The handler keeps static state and exposes methods like runBeforeUpdate, runAfterUpdate. This centralizes recursion control and makes testing easier.
4) Avoid unnecessary DML inside trigger context
Where possible, avoid performing DML on the same object from within the same trigger. Use future/Queueable/Platform Events or batch jobs for follow-up processing that must perform additional DML — this moves work to a separate transaction and avoids re-entry.
5) Check field values and use Trigger.isBefore / Trigger.isAfter carefully
Design triggers to only run when meaningful changes occur — e.g., check if a field value actually changed (compare Trigger.oldMap and Trigger.newMap) before performing DML that might cause recursion.
Example: Safe update using Set guard
public class AccountTriggerHandler {
public static Set
public static void beforeUpdate(List
List
for(Account a : newList) {
if(!processed.contains(a.Id) && a.Important_Field__c != oldMap.get(a.Id).Important_Field__c) {
a.Some_Other_Field__c = 'derived value';
toUpdate.add(a);
}
}
if(!toUpdate.isEmpty()) {
update toUpdate; // this would re-enter triggers, but processed ids will block reprocessing
processed.addAll((Id[])toUpdate.stream().map(x->x.Id).toArray());
}
}
}
trigger AccountTrigger on Account (before update) {
AccountTriggerHandler.beforeUpdate(Trigger.new, Trigger.oldMap);
}
Notes and gotchas
– Static variables are per-transaction. They reset on a new transaction (e.g., asynchronous jobs) — this is usually desired.
– Never rely on a static flag alone if you need record-level idempotency; prefer Sets of Ids.
– Be careful when tests run multiple DML statements in the same test method — static guards will persist for the transaction.
– Avoid catching and swallowing exceptions that prevent the processed-state from being set — leaving state inconsistent can cause reprocessing or skipped processing.
Conclusion
To avoid recursion in triggers, adopt a clear guard strategy: use static flags or static Sets to track processed work, centralize logic in handler classes or a framework, minimize in-trigger DML, and use asynchronous processing where appropriate. These patterns improve reliability, maintainability, and protect against governor limit failures.








Leave a Reply