1

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());
        }
    }
}
6
  • 6
    It would help if you provide the entire text of the error message verbatim. That aside, I don't see an @future method anywhere in your provided code. If your AuthCallout method is the one with the @future annotation, you'll also need to specify callout=true. Calling a future method inside of a loop is also a bad idea (the governor limit for @future calls is 50 per transaction).
    – Derek F
    Mar 1, 2017 at 20:28
  • Update failed. First exception on row 0 with id 003o0000019gMu6AAE; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, xrx_mf_crm.ContactWebserviceCall: execution of AfterUpdate
    – Dinesh
    Mar 1, 2017 at 20:50
  • The callout happens only when the record meets certain criteria. that's the reason for not worrying about governors limit. But am reading @future method, we cannot perform update to any record. is that true?
    – Dinesh
    Mar 1, 2017 at 20:55
  • 1
    It still feels like you're not including the entire error message (also, please edit your question to include that, rather than putting it in the comments). Regarding callouts, there's some confusion in terminology here. An @future callout is simply calling an @future annotated method. Those are limited to 50 per transaction, and if you have more than 50 Contacts being inserted together you're risking going over that limit. In other words, your trigger is not properly bulkified.
    – Derek F
    Mar 1, 2017 at 21:13
  • @DerekF I added the entire error message i got from debug log. Yes you are correct on governors limit. But my main issue is updating the contact record, if that happens, i will be able to manage the limit issue later.
    – Dinesh
    Mar 1, 2017 at 21:39

1 Answer 1

6

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));
}
1
  • Thanks a lot Derek. Your solution resolved both callout and governors limit problems.
    – Dinesh
    Mar 2, 2017 at 2:59

You must log in to answer this question.

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