2

When I am calling future method from a trigger then I am getting error USER_DEBUG caused by: System.AsyncException: Future method cannot be called from a future or batch method: ExampleFuture.UpdateLeadFuture(List).

The task of trigger is to check if phone number on Lead object is updated then call the future method to update the description on lead object. Why I am getting this error and why the description is not getting updated?

enter image description here

trigger TriggerToCallFuture on Lead (before update) {
    List<Id> lstupdate = new List<Id>();
    if(trigger.isExecuting && trigger.isBefore && trigger.isUpdate)
    {
        for(Lead le : trigger.new)
        {
            if(le.Phone != trigger.oldmap.get(le.Id).Phone)
            {
                lstupdate.add(le.Id);
            }
        }
        ExampleFuture.UpdateLeadFuture(lstupdate);
            
    }
}

//Update lead's description in future method, when phone number is updated
public class ExampleFuture {

    @future
    public static void UpdateLeadFuture(List<Id> lstRecord)
    {
        try{
        List<Lead> lstLead = [Select Id,Description from Lead where Id=:lstRecord];
        List<Lead> lstUpd = new List<Lead>();
        if(lstLead.size()>0)
        {
            for(Lead lst : lstLead)
            {
            lst.description = 'Updated by future method';
            lstUpd.add(lst);
            }
        }
        if(lstUpd.size()>0)
        update lstUpd;
        }
        catch(Exception e)
        {
            System.debug(e.getMessage());
        }
    }
}
0

2 Answers 2

10

The error message does a pretty good job of describing the issue here.

  1. You have a trigger on Lead that calls an asynchronous @future method
  2. Your @future method updates your Leads
  3. Which causes your Lead trigger to run again
  4. Which attempts to call your @future method again, but because you're already in a transaction that started from an @future call, Salesforce throws your error
  5. Because you encounter an error, the work you do in your @future method is rolled back

Salesforce just saved you from potentially starting an infinite loop. It wouldn't have looped again here because you are checking for a change in Phone, but performing DML on the same SObject that your trigger is for (or code called from a trigger as is the case here) is dangerous. You need to take into consideration how you're going to prevent things from going into an infinite loop.

Given the code you have right now, there is no reason to go async here. Updating the description is something you should do from within the before update trigger. The entire point of a before trigger, as opposed to an after trigger, is to allow you to make changes to the record without the need to use DML.

Honestly, given what you have right now, even a trigger is overkill. You could accomplish this with a before-save record triggered flow. The benefit of that approach is that you wouldn't need to write a test class (which you would need to write if you do this via a trigger).

If you're using this to gain experience with async apex, then you need to detect when you're inside of an @future or batch context so you can prevent another call to your @future method (and thus keep yourself from getting getting that error, or getting into an infinite loop). Another approach to consider would be to keep track of records that you've already updated (using something like a Set<Id>) so that you can prevent them from getting into your list of items that need updating.

The System class has some methods to help you out here. System.isFuture() and System.isBatch(). You will need to (at the very least) wrap your call to your @future method inside of an if block that checks to see if you're already in a future/batch context.

if(!System.isFuture() && !System.isBatch()){
    // your future method call here
}

Another thing you should do is to get rid of that try/catch block in your @future method. If you're catching an exception, you should catch a specific exception rather than just the generic Exception class. When you use try/catch, the catch block should also do something meaningful (like sending an email to notify someone that an error has occurred).

What you have right now in your catch block is effectively useless. It causes you to consume the error and move on like nothing bad happened. Swallowing/consuming errors like this makes it a lot harder to detect and debug errors. In nearly all cases, if something goes wrong, you want to know about it.

2
  • 1
    Wish I could +5 this for your detailed explanation, for delineating what to do if the OP is attempting to use this code in the "real world" or to learn, and for additional pointers/help.
    – Moonpie
    Commented Jan 14, 2022 at 17:10
  • 1
    Well explain, but "The benefit of that approach is that you wouldn't need to write a test class" -> you should have unit test that cover every logic, even it is not required by salesforce. Commented Nov 23, 2023 at 10:12
2

in your future method you are updating lead record which will again call lead trigger logic and future method will be called again resulting in mentioned error.

you can optimized your code to call future method only when required.

like you can check lstUpdate list size >0 and current context is not future context.

if(lstUpdate.size()>0  && !System.isFuture()){
        ExampleFuture.UpdateLeadFuture(lstupdate);
}

You must log in to answer this question.

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