function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
FippyFippy 

"Future method cannot be called from a future" error in trigger

Hello all,

 

@future's seem to cause a lot of fun and games, but this one has me foxed. I'm sure someone has solved this one.

I have a trigger on a case change. (Some pretty wierd code in here = legacy)

 

 

trigger caseChangeTrigger onCase (beforeinsert, beforeupdate) {

 

 Set<Id> ownerIds = new Set<Id>(); 

 Map<Id,Id> map1 = new Map<Id,Id>();

 Map<Id,boolean> map2 = new Map<Id,boolean>();

 Map<Id,Id> map3 = new Map<Id,Id>();

 

    Case[] cs = trigger.new;

    for (Case c : cs) 

    {

        ownerIds.add(c.OwnerId); 

        map1.put(c.Id,c.OwnerId);

    }

    

    for(User u : [select Id, Out_Of_Office__c, User_Backup__c from User where Id in : ownerIds])

    {

        map2.put(u.Id,u.Out_Of_Office__c);

        map3.put(u.Id,u.User_Backup__c);

    }

    

    for(Case c : cs)

    {

        if (map2.get(map1.get(c.Id)) == true ) { 

            c.Case_Owner_Backup__c = map3.get(map1.get(c.Id));

        } 

        else {

            c.Case_Owner_Backup__c = null;

        }

            

      if(c.accountid==null && c.RecordTypeId=='012500000001BVb')

      {

        c.accountid = '0015000000PJ7ec';

      }   

      

      // Sync Case with JIRA Issues

      String systemId = '1';

      String objectType ='CASE';

      String objectId = c.id;

      //Execute the sync

      JIRAConnectorWebserviceCallout.syncCase(jiraURL, systemId ,objectType,objectId);      

    } 

}

 

It's that final block that causes all the problems. 

and that syncCase does a call to a remote system. (Pretty common thing to do, I've noticed.) It begins...

 

global class JIRAConnectorWebserviceCallout {

    @future (callout=true)

    WebService static void syncCase(String jiraURL, String systemId, String objectType, String objectId) {

.... etc,

 

 

And the error is:

 

caseChangeTrigger: execution of BeforeUpdate

 caused by: System.AsyncException: Future method cannot be called from a future or batch method: JIRAConnectorWebserviceCallout.syncCase(String, String, String, String)

 

I've seen cases where a @future is called within a trigger, and the trigger shouldn't be firing multiple times, since I don't have any batch scripts running.

 

I'd appreciate any ideas and recommendations,

Thanks,

Graeme

 

Best Answer chosen by Admin (Salesforce Developers) 
Starz26Starz26

Using a static variable, setting it in the futur method, then checking for it in the trigger.

 

public class shouldIRun{

 

Public Static boolean allow = true;

 

public static void stopTrigger(){

   allow = false;

}

 

public static boolean canIRun(){

  return allow;

}

 

}

 

in the futur method call shouldIRun.stopTrigger(); BEFORE the dml call

 

in the trigger check shouldIRun.canIRun(); and if = false do not run the code in the trigger

 

Simplify as in the example given in your link if desired

All Answers

Anup JadhavAnup Jadhav

According to the docs:

You cannot call a method annotated with future from a method that also has the future annotation. Nor can you call a trigger from an annotated method that calls another annotated method.

 Does your future method, syncCase, directly or indirectly create or update the case object which in turn fires the trigger. I'd enable the debug logs, and look at the sequence of events. I am guessing that the trigger is fired because of a dml action inside the future method.

 

     - Anup

FippyFippy

Hello Anup,

Here's the complete class for syncCase - it's not long:

 

global class JIRAConnectorWebserviceCallout {

    @future (callout=true)

    WebService static void syncCase(String jiraURL, String systemId, String objectType, String objectId) {

 

        //Set your username and password here

        String username = 'xxxxxx';

        String password = 'xxxxxx';

 

        //Construct HTTP request and response

        HttpRequest req = new HttpRequest();

        HttpResponse res = new HttpResponse();

        Http http = new Http();

 

        //Construct Authorization and Content header

        Blob headerValue = Blob.valueOf(username+':'+password);

        String authorizationHeader = 'Basic ' + EncodingUtil.base64Encode(headerValue);

        req.setHeader('Authorization', authorizationHeader);

        req.setHeader('Content-Type','application/json');

 

        //Construct Endpoint

        String endpoint = jiraURL+'/rest/customware/connector/1.0/'

        +systemId+'/'+objectType+'/'+objectId+'/issue/synchronize.json';

 

        //Set Method and Endpoint and Body

        req.setMethod('PUT');

        req.setEndpoint(endpoint);

        //req.setBody('{"project":"'+projectKey+'", "issueType":"'+issueType+'"}');

        req.setBody('');

 

        try {

           //Send endpoint to JIRA

           res = http.send(req);

        } catch(System.CalloutException e) {

            System.debug(res.toString());

        }

    }

 

}

 

As you can see it doesn't modify the case or execute any triggers. It doesn't generate a CalloutException either.

Graeme

 

BritishBoyinDCBritishBoyinDC

Add a debug statement in the trigger, and then track the code in the debug log - an insert will often also trigger an update if there is a workflow or something else updating the record, regardless of your code - it is the entire context that matters...

 

If the debug does appear twice, use a static variable to prevent the @future part from firing twice... 

Anup JadhavAnup Jadhav

It's worth trying BritishBoyInDC's suggestion. Going through the debug log is admittedly not the most fun thing in the world, but it will help you squash this snarky bug. :)

 

- Anup

FippyFippy

It does indeed look as if my @future method is being called 3 times in the same context.

 

So I'm looking at your static code example here: http://boards.developerforce.com/t5/Apex-Code-Development/problem-using-future-method/m-p/276785/highlight/true#M48325

 

I'm assuming that all these classes get spawned for each web request, rather than being permanent singletons? Otherwise, there is nothing to force the boolean flag back to false again, once it has been set to true?

 

Wouldn't each trigger cause an instance of the recurseiveHelper of be created, thus setting the boolean false?

 

I'm not a full-time Salesforce developer, so I don't fully understand its stack.

 

Thanks,

Graeme

 

Starz26Starz26

Using a static variable, setting it in the futur method, then checking for it in the trigger.

 

public class shouldIRun{

 

Public Static boolean allow = true;

 

public static void stopTrigger(){

   allow = false;

}

 

public static boolean canIRun(){

  return allow;

}

 

}

 

in the futur method call shouldIRun.stopTrigger(); BEFORE the dml call

 

in the trigger check shouldIRun.canIRun(); and if = false do not run the code in the trigger

 

Simplify as in the example given in your link if desired

This was selected as the best answer
BritishBoyinDCBritishBoyinDC

Static on the Salesforce platform is a bit different - it is static for the duration of the Context...which is why it works here...

 

Your code currently triggers the callout on before insert and before update...but you only want the web callout to happen once...Salesforce considers the insert and update caused by the same action (e.g. an insert that then triggers a worflow that then triggers an update) to be a single Context...so depending on the use case, you use the static variable as just that - something that remains static during the execution of the Context, and use that to control if you want your code executed or not...

 

So assuming you just want to execute the callout the first time the code runs, you would wrap the callout code in a simple check for the value of that static boolean - let's say we default it to true - and if it is true, we make the @future call but then set the boolean to false. Now the update occurs - but because we are still in the context of this insert/update, the static is still the same variable, and is now set to false - so we don't now do the @future callout and the update process completes.

 

Insert a new record, and it's now a new context, and when we check the value of the static boolean, it will be true because the class is created again for this context.

 

Hope that makes sense!

Mayank_JoshiMayank_Joshi

This helps me in resolving the “MIXED_DML_OPERATION” error . Thanks .

 

BTW, is this some kind of recursive trigger example as well ?

 

"My Issue was I was getting above error then I chose @future to resolve but in additonal it doesn't allow me to call Batch class (a future class can't call another future or batch class) . " 

FippyFippy

Does the salesforce sandbox somehow ignore all these limits?

 

a) I never get the @future error on the sandbox, only live, but they are running same code.

 

b) Running on the sandbox with the changes you just suggested: If I post a comment to a case - a single action - it generates 2 triggers and 3 calls to my @future handler, and in every case the boolean is true and so it runs the @future code.

 

I've wrapped the call to the @future handler (in the trigger) with:

 

      if (shouldIRun.canIRun()) {

 

I call the stopTrigger() function right at the top of the @future handler, and my class is:

 

 

public with sharing class shouldIRun{

 

Public Static boolean allow = true;

 

public static void stopTrigger(){

   allow = false;

}

 

public static boolean canIRun(){

  return allow;

}

}

 

According to my debug logs, this semaphore boolean isn't functioning at all. Am I being stupid here? ;)

Graeme

 

FippyFippy

In fact, isn't this guarenteed not to work...

 

@future methods are run by the system at any time in the future right? The system knows that they are asynchronous and don't have to block the current thread. So isn't it possible that the trigger will spawn multiple copies of the @future method, but because there is no actual check for the semaphore in the @future method, it will still run multiple times, regardless of what the semaphore boolean says.

 

So I changed  the trigger code to put the stopTrigger() outside of the @future method:

 

if (shouldIRun.canIRun()) {

     // lock out future runs of this class in the same context

     shouldIRun.stopTrigger();

     

      //Execute the sync

      JIRAConnectorWebserviceCallout.syncCase(jiraURL, systemId ,objectType,objectId);

}

 

This solved the problem on the sandbox. The debug logs only showed a single call to the @future method (syncCase). Great.

 

BUT... This fix doesn't work on the live Salesforce, I still get the @future error.

Huh??

 

Graeme

 

BritishBoyinDCBritishBoyinDC

You are correct in your last post - the purpose of this technique is to avoid the JIRAConnectorWebserviceCallout.syncCase being called more than once in the same trigger - so you should check if it is true in the trigger where you call the web callout, fire the callout to your JIRA method, and then set it to false to avoid it being called again. 

 

Is all this code deployed to the live system? If so, I guess put a debug on the boolean, and check it is being set to false, and if so, when is it being set back to true?

BritishBoyinDCBritishBoyinDC

Sorry - I think I have also just realized where your problem is -  I think the web callout is updating the case, which is triggering the case update code?

 

In which case, you do still need the code to stop the @future being called multiple times BUT you also need something else - you need the case update code being triggered by the web callout to never call the jira class - since that is an @future calling another  @future.

 

Winter 12 provided a solution:

http://developer.force.com/releases/release/Winter12/New+System+Methods

 

Use the IsFuture check to add an extra check to see if the case update is being triggered by the JIRA callout and if so, do not execute the JIRA callout again.

FippyFippy

Thanks for that. I think you've just given me the solution but not as you intended.

 

I found out last night that there is a scheduled batch job constantly starting (that I wasn't aware of), that causes the trigger to fire, so I think that is causing the error, even in the legitimate single case where I am calling the JIRA function. This batch process doesn't run on the sandbox, so there's my difference! The JIRA function itself does not modify the case, but syncs it to a remote site.

 

I was looking for a way to tell if I am running the trigger from a batch process, and your link mentioned the isBatch(). I think if I wrap my JIRA call in both isFiuture() and isBatch(), I should be ok. I'll report back.

FippyFippy

I think we cracked it, thanks to all your help. :) So far so good. I just learned a lot from you guys.

 

The winning line was to wrap the call to my @future method in:

 

if (shouldIRun.canIRun() && !system.isBatch() && !system.isFuture()) {

 

Graeme

APEX 123APEX 123

I am not getting the value of potential in this program can any crack this error?

the previous program i am getting error too many future call 11.like this i am getting the error while deploying .then i converted my code in this way .it is not calculating the potential value. can you crack it where it went wrong?



Trigger :

trigger quotepotential on Quote_Line_Item__c (after insert, after update) {
    if(System.isFuture()) {
        return;
    }
    asyncApex.processAccounts(Trigger.newMap.keySet());
}

class :

global class asyncApex {
    @future public static void processAccounts(Set<Id> quoteIds) {
        Map<Id, Quote__c> quotes = new Map<Id, Quote__c>();
        for(Id quoteId: quoteIds) {
            quotes.put(quoteId, new Quote__c(Id=quoteId, Potential__c=0.0));
        }
        for(AggregateResult ar:[SELECT Quote1__c Id, SUM(Max_Batch__c) sumMax FROM Quote_Line_Item__c WHERE Quote1__c IN :quoteIds GROUP BY Quote1__c]) {
            quotes.get((Id)ar.get('Id')).Potential__c = (Decimal)ar.get('sumMax');
        }
        update quotes.values();
    }
}
EdoEdo

Thank you for sharing.
This solved my problem when trigger was itself after first call (updating record with results of call)

if(System.isFuture()) {
        return;
    }