[codeLive] Preventing Trigger Recursion

Soobin Kittredge
3 min readNov 6, 2021
Photo credit: Joel Fulgencio on Unsplash

Korean version of this article can be found here.

Trigger & Trigger Recursion

Trigger is the most customizable and powerful ways to implement business logic and more into sObject record change.

When not careful, a trigger can cause a recursion —simple example would be account update trigger inserts contact, contact insert trigger updates account. Salesforce will not let this happen forever, and will cancel the transaction when 16+ triggers get involved and throw Maximum trigger depth exceeded error. Majority of Salesforce automation depends on DB update, this can be a big issue for a service with big & complex Salesforce instance.

The CodeLive attached above discusses knows solutions on preventing a trigger recursion, and their problems. This seemed like a great video that anyone can benefit from, so I decided to write this article on it.

Using static variable to set trigger initiation limit

One of the most known ways to resolve a trigger recursion is to create a static variable in the trigger handler class’s constructor to run once/set amount of times for this handler to run. After the handler ran once/certain amount of times, handler skips any further execution.

Simple example of the trigger recursion between Account and Task

As a simple example, let’s say AccountTriggerHandler has a logic to create a new task when account gets inserted or updated. And a TaskTriggerHandler that updates related account’s Newest_Task__c field when created.

In below example handler class, if the logic on creating a new task gets limited by runOnce() that portion of the logic will run once when an Account gets inserted, but will not run again when TaskTriggerHandler updates the account.

In the codeLive video, Brian mentions that this ignores the assumptions on how Salesforce works and thus should be rejected.

For example, when a bulk record update/insert partially fails and gets retried, the static variable stays the same as it is within same transaction and skips the logic. Unit test might fails. Also when 200+ records are inserted/updated/deleted/undeleted, first 200 records will go through the logic while the rest will skip it since previous batch changed the static variable, as all are processed in one transaction.

Collecting processed IDs in Set<Id> collection

Set.addAll(Set) will return true if original set changed as a result of addAll

A bit upgraded way of utilizing static variable. This method collects the IDs that has been through the trigger handler for a comparison. Set.addAll(Set) will return true if original set changed as a result.

This will resolve the issue related to processing 200+ records, but issue with retrying or unit test stays.

Process only when record was changed

filterOutUnchangedRecords() method will compare previous value ( Trigger.old ) to the updated value ( Trigger.new ) and will only process if the value has been changed. This prevents the handler from doing more DML on records that hasn’t been changed.

This method makes sense in a way that this prevents unneeded transaction, but this will not be able to prevent separate triggers updating field values repeatedly.

Count the processing by LoopCounter

TL;DR: The main focus of the code is between line 12 and line 20.

run() runs the trigger logic processing, validates the input then increment the counter addToLoopCount() and the counter gets decremented once the trigger logic has been completed removeFromLoopCount();

This method works for bulk processing with 200+ records as the each batch will increment and decrement the counter before the next batch starts processing.

Partial retry works as well since the counter would be back to 0 by the retry triggers the handler. When used Test.startTest() and Test.stopTest() Unit test will not cause error.

With great power there must also come great responsibility

As powerful the Apex Trigger is, the more need to be careful implementing the solution. Best way preventing the trigger recursion is to understand data model of your org and their DML points before writing a new handler that would include DML.

--

--

Soobin Kittredge

Salesforce Application Architect | Senior Salesforce Developer.