Recursion Handling … Recursion Handling…

Deep Banerjee
4 min readOct 13, 2022

in Salesforce platform

Let’s Consider a scenario, where a trigger will fire in after update context if the old value does not match the new value and then makes a callout.

And, you also have a workflow that updates the same object when it’s entry criteria is satisfied for the same update event.

Interestingly, in the Salesforce platform, Workflow field updates will cause the trigger to re-execute AND, the value of Trigger.old will be as it was when the record was initially updated.

Trigger.old contains a version of the objects before the specific update that fired the trigger. However, there is an exception. When a record is updated and subsequently triggers a workflow rule field update, Trigger.old in the last update trigger won’t contain the version of the object immediately prior to the workflow update, but the object before the initial update was made

Thus, your future method will be called twice, and if it was making a callout, the callout will be made twice.

This is a classic example of recursion in a real force.com scenario!

So, how can we prevent this recursion?

The Amateur Solution :

Using a static flag ( boolean) in your trigger handler

Though this solution sounds promising, but there are problems with this approach

The first problem :

Apex trigger fires in chunks of 200!

So, if you’re trying to update let’s say 800 records via data loader, then the trigger will fire 4 times. And every time it fires, the static flag will be reset. Hence, making it a useless solution in terms of scalability.

The Slightly Better Solution ( what most developers use )

A better way to handle recursion is by doing it by a Static Set<Id>

Set of Ids, is a bulkified way of handling recursion , as this will contain all the ids that were already processed

Though this solution will solve our Data loader problem and is a scalable solution, still there’s one issue

The next Problem :

if your Trigger is also invoked in a use case where partial success is allowed and one or more of the records fails to validate? AllOrNone = false can happen in many common use cases:

  • Data Loader
  • Any use of Apex Database.update(records,false); True also for the other Database.xxx methods.
  • Bulk, SOAP, or REST APIs that either default AllOrNone to false or set explicitly if available.

Going back to the Triggers and Order of Execution, there’s one last tidbit as to why you can’t use static variables for recursion control in an AllOrNone = false use case:

When a DML call is made with partial success allowed, more than one attempt can be made to save the successful records if the initial attempt results in errors for some records. For example, an error can occur for a record when a user-validation rule fails. Triggers are fired during the first attempt and are fired again during subsequent attempts. Because these trigger invocations are part of the same transaction, static class variables that are accessed by the trigger aren’t reset. DML calls allow partial success when you set the allOrNone parameter of a Database DML method to false or when you call the SOAP API with default settings.

So, till now we saw two solutions :

A Using Static Boolean → Amateur solution

(i) as it breaks, for DML of more than 200 records

(ii) breaks with partial retries

(iii) breaks unit tests ( for same record is updated for more than once)

B. Using Static Set<Id> → Slightly better solution

(i) breaks with partial retries

(ii) breaks unit tests ( for same record is updated for more than once)

So, basically, we cannot solve recursion effectively using static variables.

We need something more concrete to prevent recursion

A Workable Solution :

We need to get hold of the transactions , and find out exactly which transaction has been completed . We can achieve this by having a Transaction Service class in place.

And finally, in the trigger, we can check for unvisitedIds

The triggerhandler asks the Transaction Service to get all unvisited Ids for some context scope. Behind the scenes, the TransactionService saves the Ids + context scope + TransactionId in the database, thus creating a persistent store for the AllOrNone = true use case and a rolback-able store for the AllOrNone = false use case.

So, finally, we have found our solution!

Let me know, if you have better solution ..

Link to Transaction Service Class

--

--