Intermediate 25 min read

Trigger Recursion

By the end of this lesson, you'll be able to:

  • Explain how a trigger can accidentally cause itself to fire again
  • Use a static Boolean flag to prevent runaway recursion
  • Recognize the symptoms of an unguarded recursive trigger

Prerequisites: "Avoiding Logic in Triggers"

How a trigger can trigger itself

trigger AccountTrigger on Account (after update) {
    for (Account acc : Trigger.new) {
        acc.Description = 'Last updated: ' + System.now();
    }
    update Trigger.new; // this update fires AccountTrigger again!
}
  1. trigger AccountTrigger on Account (after update) { — an after update trigger.
  2. for (Account acc : Trigger.new) { — loops over the just-saved records.
  3. acc.Description = 'Last updated: ' + System.now(); — changes a field in memory.
  4. } — closes the loop.
  5. update Trigger.new; — performs a fresh update on Account records — this is Module 9's recursion concept, but happening accidentally through the trigger/DML relationship rather than a method deliberately calling itself.
  6. } — closes the trigger.

Warning: Line 5 fires AccountTrigger again, which updates again, which fires again — unguarded, this runs until it hits a governor limit (typically "Too many DML statements" or "Apex CPU time limit exceeded"), crashing the transaction.

Guarding with a static flag

public class AccountTriggerHandler {
    private static Boolean hasRun = false;

    public void afterUpdate(List<Account> updatedAccounts) {
        if (hasRun) {
            return;
        }
        hasRun = true;

        for (Account acc : updatedAccounts) {
            acc.Description = 'Last updated: ' + System.now();
        }
        update updatedAccounts;
    }
}
  1. public class AccountTriggerHandler { — the handler class.
  2. private static Boolean hasRun = false; — this is Module 10's static fields — one shared value per class, not per object — put to direct use.
  3. (blank line)
  4. public void afterUpdate(List<Account> updatedAccounts) { — the method.
  5. if (hasRun) { — checks the guard first.
  6. return; — hasRun persists across the recursive re-entry within the same transaction, so the second (recursive) call sees hasRun == true and exits immediately.
  7. } — closes the if.
  8. hasRun = true; — marks that this logic has now run.
  9. (blank line)
  10. for (Account acc : updatedAccounts) { — the same loop as before.
  11. acc.Description = 'Last updated: ' + System.now(); — the same change.
  12. } — closes the loop.
  13. update updatedAccounts; — this update fires the trigger again, but the guard clause above stops it from updating and re-triggering a second time.
  14. } — closes the method.
  15. } — closes the class.

Recognizing the symptoms

An unguarded recursive trigger typically shows up as a governor limit error — often "Too many DML statements" or "Apex CPU time limit exceeded" — from what looks like a single, simple update. If a trigger's error message mentions far more DML operations than the actual number of records being saved, unguarded recursion (rather than the SOQL/DML-in-a-loop problems from Module 7) is a strong suspect worth checking first.

Exercise

Add a static recursion guard to this handler method so it only runs its logic once per transaction.

Show hint

private static Boolean hasRun = false; then check and set it at the top of the method.

APEX

Trigger Recursion Quiz

1. Why does a static Boolean flag work as a recursion guard?

Log in to submit the quiz and save your score.

My Notes

Log in to keep private notes on this lesson.

Questions about this lesson

No questions yet — be the first to ask.

Log in to ask a question about this lesson.

Summary

A trigger that performs DML on its own object can accidentally trigger itself again — trigger recursion — and needs a guard to stop it from running out of control.