System.LimitException: Maximum trigger depth exceeded
What does this error mean?
Salesforce enforces a maximum trigger execution depth of 16 levels. Each time a trigger fires and causes another DML operation that fires another trigger, the depth increases. When triggers recursively call themselves — or chain deeply between objects — the depth ceiling is reached and this exception is thrown.
This error is distinct from the "Too many SOQL queries" or "Too many DML statements" errors. It specifically tracks how many nested trigger executions are active simultaneously, not the total number of queries or DML statements issued.
Common Causes
1. Trigger updates a field that re-fires the same trigger
An Account trigger that updates a field on Account will fire the Account trigger again. Without a recursion guard, this loops infinitely until the depth limit is hit.
2. Cross-object trigger chains
Trigger A on Account updates Contact → Contact trigger updates a Case → Case trigger updates Account → Account trigger fires again. Even across different objects, deep chains exceed the limit.
3. Workflow rules or Process Builder firing triggers
A trigger updates a field, a workflow rule fires an additional field update on the same record, which fires the trigger again — creating a recursion Salesforce considers a depth increase.
How to Fix It
Solution 1: Use a static Boolean recursion guard
A static variable in Apex persists for the lifetime of a transaction. Set it to true on first trigger entry and check it at the start of every trigger to skip re-entry.
// TriggerGuard.cls — reusable across all triggers
public class TriggerGuard {{
private static Set<String> executed = new Set<String>();
public static Boolean hasRun(String context) {{
return executed.contains(context);
}}
public static void setHasRun(String context) {{
executed.add(context);
}}
public static void reset(String context) {{
executed.remove(context);
}}
}}
// AccountTrigger.trigger
trigger AccountTrigger on Account (after update) {{
if (TriggerGuard.hasRun('AccountTrigger.afterUpdate')) return;
TriggerGuard.setHasRun('AccountTrigger.afterUpdate');
AccountTriggerHandler.handleAfterUpdate(Trigger.new, Trigger.oldMap);
}}
Solution 2: Redesign cross-object trigger chains
If multiple triggers fire across objects in sequence, consider flattening the logic. Instead of each trigger updating the next object synchronously, use a single scheduled job or Batch Apex to apply changes in one pass — breaking the chain entirely.
Pro Tip: Use a Set-based guard (as shown above) instead of a simple Boolean. A Set lets you track recursion separately per trigger context (e.g., AccountTrigger.afterUpdate vs AccountTrigger.beforeInsert), preventing false positives when the same trigger legitimately fires in different contexts.