[SalesForce] Not able to call @future from @future

I am trying to call @future method from trigger and got CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY Error, because of update operation within the @future method. Class and triggers given below.

Error Message

Update failed. First exception on row 0 with id 003o0000019gMu6AAE; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, xrx_mf_crm.RepWebserviceCall: execution of AfterUpdate

caused by: System.AsyncException: Future method cannot be called from a future o.

Class

@future(callout=true)
public static void AuthCallout(Set<Id> contactIds) {

    List<Contact> ContactUpdate = [SELECT id  FROM Contact where Id IN :contactIds];
    String ContactID;

    HttpRequest req = new HttpRequest();

    req.setTimeout(60000); 
    req.setHeader('Accept','*/*');
    req.setHeader('Content-Type','application/json'); // Content Type
    req.setMethod('GET'); 

    for (Contact c : ContactUpdate)
    {    
        ContactID = c.id;

        req.setEndpoint('https://xxx/xxxx/xxxxx/xxx/xxx-lookup?ContactID=' + ContactID);

        Http http = new Http();

        HTTPResponse res = http.send(req);
        System.debug(res.getBody());

        JSONParser parser = JSON.createParser(res.getBody());

        String GMMID;
        while (parser.nextToken() != null) {
            if ((parser.getCurrentToken() == JSONToken.FIELD_NAME) && (parser.getText() == 'GCGMM')) {
                // Get the value.
                parser.nextToken();
                // Compute the grand total price for all invoices.
                GMMID = parser.gettext();
            }
        }
        
        //ContactUpdate.IsFutureContext__c = true;
        C.Group_ID__c = GMMID;
        update c;
    }
}

Trigger:

trigger ContactWebserviceCall on Contact (After insert) {
    for (Contact co : Trigger.new)
    {
        If(co.Rep_ID__c.substring(0,3) == 'CRM')
        {
            ContactWebServiceCall. AuthCallout (Trigger.newMap.keySet());
        }
    }
}

Best Answer

The problem you're having isn't that you can't call an @future method from a trigger, but rather that you end up trying to call an @future method when you're already in an @future context.

It isn't happening because of your trigger, but somehow, running a DML update on your Contacts in your @future method is causing your @future method (or perhaps some other @future method) to be called again. It can't be because of the trigger that you've provided because that trigger only runs After Insert (not on an update).

The likely solution here is to add some extra code that exercises some extra caution when handling @future calls.

The basic example of the pattern I'm talking about is this

public class SaferFutureCall{
    // In this method, which you'll note is not given the @future annotation, you'll
    //   place the code that you have in your current, not-as-safe, @future method
    private static void doNormal(List<Id> idList){
        List<Contact> ContactUpdate = [SELECT id  FROM Contact where Id IN :idList];
        String ContactID;

        HttpRequest req = new HttpRequest();

        req.setTimeout(60000); 
        req.setHeader('Accept','*/*');
        req.setHeader('Content-Type','application/json'); // Content Type
        req.setMethod('GET');

        // ...and the rest of your current code
    }

    // This method doesn't look like it does too much, it's all we need to do to
    //   execute the 'doNormal' method above in an @future context.
    @future(callout=true)
    private static void doFuture(List<Id> idList){
        workToDo(idList);
    }

    // This is where the magic happens
    public static void execute(List<Id> idList){
        if(System.isFuture() || System.isBatch()){
            // If we're in an async context that doesn't allow @future callouts,
            //   we can just simply call the non-@future version of the method.
            // This avoids the "can't call @future from @future" error.
            doNormal(idList);
        } else {
            // If we're not in an async context, we can call the @future version
            //   (provided you haven't hit the limit yet)
            if(Limits.getFutureCalls() < Limits.getLimitFutureCalls()){
                doFuture(idList);
            } else {
                // place some code in here to explicitly notify you that your request
                //   to run an @future method was denied.
            }
        }
    }
}

Using this class is pretty straightforward, you'd replace your direct call to the @future method with

SaferFurure.execute(someIdList);

With that out of the way, we can turn attention to removing the @future call from inside a loop.

Given your provided trigger, it appears that you might not want to call your @future method with every record being inserted. I also doubt that you want to be repeatedly calling your @future method with the same data multiple times (which is a very likely possibility with the trigger that you currently have).

Instead, making your trigger bulk-friendly would involve first preparing the data that you want to send to your @future method, and, in a separate section of trigger code, then calling your @future method.

An example based on your provided trigger:

trigger ContactWebserviceCall on Contact (After insert) {
    // First, initialize a collection to hold data that you'll be gathering
    Set<Id> contactIdsForCallout = new Set<Id>();

    // Loop over your trigger context variable to gather your target data
    for (Contact co : Trigger.new){
        if(co.Rep_ID__c.substring(0,3) == 'CRM'){
            contactIdsForCallout.add(co.Id);
        }
    }

    // And then pass data (once, and only once) to your callout method.
    // In keeping with my example code, and your trigger code, you have a set
    //   but SaferFuture.execute() requires a List.
    // Creating a new list, and passing the set in to the constructor can easily
    //   take care of this for us.
    SaferFurure.execute(new List<Id>(contactIdsForCallout));
}
Related Topic