4

In my Lead object before update trigger whenever certain conditions are met, the trigger calls a future method (which makes a API call to a service).

The value returned by this REST service needs to be saved in a custom field on Lead. So I am doing an lead update inside this future method.

Now because of this, it becomes a recursive update process and by that rule a recursive future method as well.

Here is the code snippet:

Lead Before Update trigger:

 List<Id> qualifiedLeadIdList = new List<Id>();

 for(Lead updatedLead : Trigger.new) {
     if(certain conditions are met) {
        qualifiedLeadIdList.add(updatedLead.Id);
     }
  }

  MyFutureCallClass.callTheAPI(qualifiedLeadIdList);

In my MyFutureCallClass

global class MyFutureCallClass {

    @future (callout=true)
    public static void callTheAPI(List<Id> qualifiedLeadIdList) {
        List<Lead> leadUpdateList = new List<Lead>();

        for(Lead leadRec : [
            select 
               field1, field2....,
                API_Response_Status__c
            from Lead
            where Id in :qualifiedLeadIdList
        ]) {
            //do the http call and get response
            //get the 'status' attribute from response
            leadRec.API_Response_Status__c = (String)resMap.get('status');

            leadUpdateList.add(leadRec);
        }

        update leadUpdateList; //here i update the leads
    }
}

I am confused on how to deal with this situation. Is there another way both the future method and trigger would work and not step on each other?

2 Answers 2

7

The general solution to this issue is to make use of static variables.

A static variable, once set, will have its value persist for the remainder of the transaction. The one big caveat to this is that static variables defined in a trigger which, while allowed in the syntax of Apex, are worthless because they aren't persisted for the entire transaction. Static variables defined in triggers are reset between trigger events (like between before update and after update) and also between trigger batches (if you update 400 leads at the same time, Salesforce will execute Lead triggers twice, breaking it up into batches of 200 records [at most]). If you want to use a static variable, you want to define it in an Apex class.

Probably the simplest way for you to break your recursion here is to add a static Set<Id> to your MyFutureCallClass.

global class MyFutureCallClass {
    // You can initialize a static variable at time of declaration (just like any other
    //   variable)
    public static Set<Id> alreadyProcessed = new Set<Id>();

    // If you don't want to do that, you could also use a static initialization block
    //   like below
    // Think of it as sort of a 'static constructor'
    //static{
    //    alreadyProcessed = new Set<Id>();
    //}

    @future (callout=true)
    public static void callTheAPI(List<Id> qualifiedLeadIdList) {
        // The last change that needs to be made in this class is we need to add the
        //   Ids that are passed to this @future method to our static set.
        // Because this is a static method, we can access class static variables
        //   without doing anything special.
        alreadyProcessed.addAll(qualifiedLeadIdList);

        List<Lead> leadUpdateList = new List<Lead>();

        for(Lead leadRec : [
            select 
               field1, field2....,
                API_Response_Status__c
            from Lead
            where Id in :qualifiedLeadIdList
        ]) {
            //do the http call and get response
            //get the 'status' attribute from response
            leadRec.API_Response_Status__c = (String)resMap.get('status');

            leadUpdateList.add(leadRec);
        }

        update leadUpdateList; //here i update the leads
    }
}

Your trigger would also need to be updated to check to see if you've already sent a given record to your @future method.

List<Id> qualifiedLeadIdList = new List<Id>();

for(Lead updatedLead : Trigger.new) {
    // The most straightforward method is to check to see if the current lead
    //   already exists in the 'alreadyProcessed' set.
    // This is the reason we use a static set instead of a list, because the Set
    //   class provides the contains() method (which is a lot faster than iterating
    //   through a list to see if there is a matching Id).
    // If alreadyProcessed _does not contain_ the Lead id, it's safe to add
    //   that Lead's Id to the list of things to send to the @future method.
    if(<certain conditions are met> && !MyFutureCallClass.alreadyProcessed.contains(updatedLead.Id)) {
        qualifiedLeadIdList.add(updatedLead.Id);
    }
}

MyFutureCallClass.callTheAPI(qualifiedLeadIdList);

In this case, adding ids to the static Set<Id> needs to be done in your @future method, and checking to see if an Id exists in the set needs to be done in your trigger.

This is because an @future call kicks off an entirely separate, asynchronous transaction. If you were to try to add Ids in your trigger, when your @future method is actually run, the static Set<Id> would be empty because you're now in a separate transaction from the one that initiated the @future call.

A slightly more efficient way to do things would be to, in your trigger, store the Ids you want to pass into your @future method in a Set<Id> rather than a List<Id>.

This would allow you to skip adding the additional condition to your if statement, and just do IdSetToProcessAsync.removeAll(MyFutureCallClass.alreadyProcessed);

+edit:
I glossed over this, but in the comments, you said that you're getting the error

System.AsyncException: Future method cannot be called from a future or batch method

Looking over your code again, that makes perfect sense. Your Lead trigger is calling an @future method. That puts you into a 'future context'. When your @future method is run, it causes your Lead trigger to be run again. When your trigger attempts to call the @future method again, you're trying to call an @future method from a 'future context', and you get your error.

The easiest way to prevent that error is to simply not call your @future method when you're in a future context. System.isFuture() tells you whether or not you're in a future context.

List<Id> qualifiedLeadIdList = new List<Id>();

for(Lead updatedLead : Trigger.new) {
    if(<certain conditions are met>) {
        qualifiedLeadIdList.add(updatedLead.Id);
    }
}

// This is the simplest way to prevent the 'future from future' error.
// If we are _not_ in a future context, then call the future method
if(!System.isFuture()){
    MyFutureCallClass.callTheAPI(qualifiedLeadIdList);
}

The static Set<Id> approach is more about controlling recursion, and allows you to prevent the same records from being operated on multiple times while still allowing unprocessed records to be processed.

In this situation, a static Boolean alreadyProcessed (rather than a static Set<Id>) would be the equivalent approach to checking if(!System.isFuture()). I'd recommend using if(!System.isFuture()) though, because using a static boolean would cause you issues if you were to ever update more than 200 Leads at once (and checking System.isFuture() would not cause those issues).

A slightly different (and more complex) approach would be to handle the future context check in your helper class using the following pattern

// The idea here is that instead of directly calling your future method, you call 
//   this runAsync() method instead.
static void runAsync(List<Id> targetIds){
    try{
        if(system.isFuture() || system.isBatch()){
            // If we're in an async context, just call the normal version of the method.
            runNormal(targetIds);
        } else {
            if(Limits.getFutureCalls() < Limits.getLimitFutureCalls()){
                // Otherwise, we can call the @future version
                runFuture(targetIds);
            } else {
                // Error out using an inner class that extends the standard Exception class
                throw new CustomException('Hit the single transaction @future call limit');
            }
        }
    }catch(Exception e){
        // Notify someone that an exception occurred.
        // This could be a chatter post, an email, running a class that integrates
        //   with Slack, etc...
    }
}

@future
static void runFuture(List<Id> targetIds){
    // Since we are guaranteed to be in a future context inside this method body,
    //   anything else that is called as part of this method is also in a future context.
    // If runNormal() contains the exact same code that you have in your existing
    //   @future method, then the effects will be the same.
    runNormal(targetIds);
}

static void runNormal(List<Id> targetIds){
    // Your existing @future code would go in here.
    // Doing so allows you to choose whether or not to run the code asynchronously.
    // In the situation where you're already in a future context, it allows you to
    //   run the code again (if you wish) without trying to start another async,
    //   @future transaction (which is not allowed for @future).
}

You would use this 'guarded @future' pattern in conjunction with the static Set<Id> pattern mostly in situations where processing one record asynchronously could lead to other records needing to be processed.

Looking at your provided code, I don't think this is the case. I'd recommend going with the simple System.isFuture() check.

4
  • Your solution works, however there is one more thing that needs to be added. The call to the MyFutureCallClass should only be done if qualifiedLeadIdList is not empty. Otherwise it shows the same error as before. Aug 30, 2017 at 8:22
  • @codeinprogress You were getting an error message? I didn't see that mentioned anywhere before now. Taking a look at it again, your error message must've been "Cannot call @future method from @future". In that case, a simple if(!System.isFuture()) to wrap your call to your @future method in your trigger would have done the job. The "static collection of Ids" method has broader applications though (like preventing trigger recursion from getting out of hand).
    – Derek F
    Aug 30, 2017 at 12:42
  • Yes I was getting "Cannot call @future method from @future". So do I remove the static id set solution and just wrap my call to future method in the if(!System.isFuture()) ?? Aug 30, 2017 at 15:11
  • @codeinprogress You certainly could, though that's a decision you'll need to make for yourself. Checking if you're in a future context is 1 additional line of code in 1 file vs the Set<Id> solution being 4 additional lines of code across 2 files. The performance will be essentially identical. Avoiding calling your @future method when qualifiedLeadIdList is empty sounds like a good idea no matter which solution you choose.
    – Derek F
    Aug 30, 2017 at 15:36
-1

Here is the blog from Jeff Douglas He has nicely explained everything in this blog.

1
  • 1
    Instead of simply posting a link to another resource as an answer, the preferred practice here is to copy and paste the relevant sections of the article/blog/answer that you're linking to in your answer (using blockquotes). The reason for that is because linked resources can be moved at a later date, or disappear entirely. Quoting the relevant sections helps preserve the content for people who may visit this question in, say, a decade from now.
    – Derek F
    Aug 29, 2017 at 12:42

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .