Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
All errors
Apex

System.LimitException: Maximum stack depth has been reached: 1001

Your Apex code recursed 1,001 levels deep. Almost always a trigger that updates the same object, fires itself, and never has a base case. The fix is a static recursion guard, not refactoring the recursion away.

Also seen asMaximum stack depth has been reached·Maximum stack depth has been reached: 1001·stack overflow apex

Apex caps the call-stack depth at 1,000 frames. Hit 1,001 and the runtime stops you. The pattern is essentially always the same: a trigger updates a record on the same object that fired it, the update re-triggers, and the chain doesn't terminate.

The shape of the bug

trigger AccountTrigger on Account (after update) {
    for (Account a : Trigger.new) {
        Account updated = new Account(Id = a.Id);
        updated.Description = 'touched at ' + System.now();
        update updated;     // re-fires this trigger
    }
}

Every update fires the trigger again. Each invocation pushes a frame; at 1,001 you blow up.

The standard fix: a recursion guard

A static Set<Id> of records you've already processed in this transaction. Static variables persist for the duration of a single transaction.

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

    public static void handleAfterUpdate(List<Account> accounts) {
        List<Account> toUpdate = new List<Account>();
        for (Account a : accounts) {
            if (processedIds.contains(a.Id)) continue;
            processedIds.add(a.Id);

            Account u = new Account(Id = a.Id);
            u.Description = 'touched at ' + System.now();
            toUpdate.add(u);
        }
        if (!toUpdate.isEmpty()) update toUpdate;
    }
}

The first run processes the records and adds their IDs to the set. The second run (recursed) finds every ID in the set and skips. The recursion stops at depth 2.

When the recursion is across triggers

If AccountTrigger updates a Contact, and ContactTrigger updates the Account, you have a cross-object cycle. Same fix, different scope:

public class TriggerContext {
    public static Set<Id> recentlyTouchedAccounts = new Set<Id>();
    public static Set<Id> recentlyTouchedContacts = new Set<Id>();
}

Each trigger checks the other set before firing its update.

When the recursion is in a method, not a trigger

Apex can legitimately recurse — tree traversal, parser walks, divide-and-conquer. The 1,000-frame limit is generous for those. If you hit it during a recursive method:

  1. Convert to iteration with an explicit stack. Allocate a List<Stuff> and while (!stack.isEmpty()) your way through it. The implicit call stack becomes an explicit data structure with no depth limit.
  2. Tail-call by hand. Apex doesn't optimise tail calls, but a while loop that consumes a state variable does the same thing without growing the stack.

Diagnosing it from the log

The debug log includes the full stack at exception time. Look for the same method name appearing 1,000 times in a row — that's your loop. If you see two method names alternating, you have an A→B→A cycle and the guard goes on whichever side is easier to predicate.

A common false fix

Trigger.isExecuting doesn't help — it's true for any execution of the trigger, not just nested ones. The recursion guard must track records you've already processed; a flag won't do it because the platform does fire the trigger again on the same records.

Related dictionary terms