When developing Apex triggers in Salesforce, one of the most common challenges developers face is recursion — a situation where a trigger causes itself to run repeatedly in an infinite loop, often due to updates made within the same object. If left unhandled, recursion can lead to performance issues, unexpected behavior, or hitting Salesforce governor limits.

In this blog post, we'll explore different approaches to avoid recursion in Apex triggers, covering both simple and scalable patterns.


🔁 What is Trigger Recursion?

Trigger recursion happens when an update in a trigger causes the same trigger to fire again, which causes another update, and so on — creating a loop. This is especially common when:

  • A trigger updates records of the same object it’s written on.
  • There are cross-object updates causing re-entry into the trigger indirectly.

✅ Approaches to Prevent Recursion in Apex Triggers

1. Static Boolean Flag (Basic Approach)

A static variable retains its value throughout the transaction, making it ideal to track whether a trigger has already executed.

Example:

public class AccountTriggerHandler {
    public static Boolean hasRun = false;

    public static void handleAfterUpdate(List<Account> accList) {
        if (hasRun) return;
        hasRun = true;

        // Your logic here
    }
}

Trigger:

trigger AccountTrigger on Account (after update) {
    AccountTriggerHandler.handleAfterUpdate(Trigger.new);
}

✅ Simple to implement
⚠️ Not suitable for bulkified or multi-object logic


2. Static Set to Track Record Ids (Bulk-safe)

Instead of using a single flag, maintain a static Set<Id> to track which records have already been processed.

Example:

public class AccountTriggerHandler {
    private static Set<Id> processedAccountIds = new Set<Id>();

    public static void handleAfterUpdate(List<Account> accList) {
        List<Account> toProcess = new List<Account>();

        for (Account acc : accList) {
            if (!processedAccountIds.contains(acc.Id)) {
                toProcess.add(acc);
                processedAccountIds.add(acc.Id);
            }
        }

        // Process only unprocessed records
    }
}

✅ Better control over which records are processed
✅ Bulk-safe
⚠️ Can grow memory usage with very large datasets


3. Custom Metadata/Custom Settings Based Flags (Org-level Control)

You can create a hierarchy custom setting or custom metadata to turn trigger logic on/off based on environment or admin control.

Use Case:

  • Disable trigger logic temporarily during data migration or batch jobs.
  • Better control for admins without changing code.

✅ Useful in admin-controlled deployments
⚠️ Not a direct solution to recursion but good for managing trigger logic


4. Trigger Frameworks with Recursion Guards

Robust trigger frameworks like the Trigger Handler Framework (developed in-house or using open source like SFDC Trigger Framework) provide structured ways to manage recursion.

Sample Framework Pattern:

public abstract class TriggerHandler {
    private static Map<String, Boolean> handlerStatus = new Map<String, Boolean>();

    protected Boolean isFirstRun(String handlerName) {
        if (!handlerStatus.containsKey(handlerName)) {
            handlerStatus.put(handlerName, true);
            return true;
        }
        return false;
    }
}

Your handler class would then call isFirstRun('AccountUpdateHandler') to decide if logic should proceed.

✅ Scalable and enterprise-ready
✅ Encourages separation of concerns
⚠️ Slightly more complex setup


5. Using a "Reentrancy Key" on Custom Fields (Last Resort)

If recursion is caused by logic that cannot be easily isolated, you may store a “reentrancy token” in a custom field to track whether the logic already ran.

Example:

Add a custom field: Last_Processed_By__c
Set it with a hash or process ID so the logic doesn’t run again for the same update.

✅ Useful in edge cases
⚠️ Involves DML and schema changes — use only when others fail


🛡️ Best Practices

  • Always bulkify your recursion guard — triggers run in batches, not one record at a time.
  • Combine multiple patterns if needed (e.g., static flag + trigger framework).
  • Consider using platform events, future methods, or queues to decouple logic that doesn't need to run synchronously.

🚀 Conclusion

Avoiding recursion in Apex triggers is critical to building robust and performant Salesforce applications. Whether you're using simple static flags or full-fledged frameworks, choosing the right approach depends on the complexity and scale of your org.

Have you faced recursion issues in your Salesforce projects? Share your experience or favorite recursion-avoidance pattern in the comments!