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
nelrib88nelrib88 

Help with limitation error on trigger and @future callout method

My setup: I have a trigger that gets fired whenever a specific custom field for a contact object is deleted.  the trigger invokes a @future apex method that sends a callout to an external system.  The trigger was designed such that from a contacts page view the field can be deleted and it would sync with the other system.

 

The problem i am facing is that there is a nightly batch script that runs and throws the error CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY:itracmedia.InvalidEmailContactTrigger: System.LimitException: itracmedia:Too many future calls: 11:-- for about 100 of 5000 records it processes. 

 

I understand that the error is caused form the @future method being invoked more than 10 times, but from what i understood from reading the documentation is that the limit is focused on one trigger instance invoking the @future method more than 10 times. I believe that my trigger invokes the @future method only once for each contact that gets updated.  is it possible to get this error if the script runs so fast that there are 10 callouts being made at the same time and the 11th starts before any have finished yet.

 

My code is below for both the trigger and apex class.

 

Can someone please explain how this is happening and if you can please help me in finding a solution.

 

trigger InvalidEmailContactTrigger on Contact (before update)
{
    private String emailissue {get; set;}
    if(Trigger.isBefore)
    {
        emailissue = null;
        for(Contact contact : trigger.old)
        { 
            emailissue = contact.Email_Issue__c;
        }
        
        for(Contact contact : trigger.new)
        {            
            if(contact.Email_Issue__c == null && emailissue != null)
            {  
                if(contact.lastName == 'ApexRunTestIssue'){
                    EmailIssueRemover.test();
                } else {      
                    EmailIssueRemover.removeEmailIssue(contact.id, emailissue, contact.session_id__c, contact.server_url__c);
                }
            } 
        }
    }
}
public class EmailIssueRemover {

  // Static variable that assumes a test is not running
  public static boolean isApexTest = false;
    
  //Future annotation to mark the method as async.
  @Future(callout=true)
  public static void removeEmailIssue(String id, String issue, String sessionid, String serverurl) {

     String server = EncodingUtil.urlEncode(serverurl,'UTF-8');
     String session = EncodingUtil.urlEncode(sessionid,'UTF-8');
   
     Http h = new Http();
     HttpRequest req = buildWebServiceRequest(id, EncodingUtil.urlEncode(issue,'UTF-8'), session, server);
     if(!isApexTest){
       HttpResponse res = invokeWebService(h, req);   
     } 
  }
    
  public static HttpRequest buildWebServiceRequest(String id, String issue, String sessionid, String serverurl){

    //Build HTTP Request object
    HttpRequest req = new HttpRequest();
    req.setEndpoint('https://www.****.com/removeEmailIssue?sessionid=' + sessionid + '&serverurl='+ serverurl + '&type='+issue+'&id='+id);
    req.setMethod('GET');
    return req;
  }
  
  public static HttpResponse invokeWebService(Http h, HttpRequest req){
     HttpResponse res = h.send(req);
     return res;
  }
  
  // Wrapper method for "main" that we will call in the Test Class
  public static void test(){
    isApexTest = true;
    removeEmailIssue('test','test','test','test');
  }
}
Best Answer chosen by Admin (Salesforce Developers) 
Damien_Damien_

I have one minor adjustment to make ot craigmh suggestion.  You cannot pass Nonprimitive objects into a future method.  Get a set of Ids to do the work on and do all of your work in the future method with those ids.

 

trigger InvalidEmailContactTrigger on Contact (bef​ore update) {
	Set<Id> contactsToUpdate = new Set<Id>();
	boolean meetsConditions = false;
	for(Contact c: trigger.new) {
		if(meetsConditions) { contactsToUpdate.add(c.Id); }
	}
	EmailIssueRemover.removeEmailIssues(contactsToUpdate);
}

All Answers

craigmhcraigmh

Triggers run in batches of 200 records. The 10 callout limit isn't for a 10 concurrent callouts, it's for the lifetime of the trigger execution.

 

My suggestion would be to change the web service to handle batch operations, then make one callout with the data for all records in the trigger after the for loop.

nelrib88nelrib88

At what point in the trigger would i be able to collect the batch of 200 records. 

 

Would you be able to provide a further explanation of how i can go about doing this, im no expert in salesforce and want to get this working right before installing it again.

 

thanks,

NR

craigmhcraigmh

It would be something like this:

 

trigger InvalidEmailContactTrigger on Contact (bef​ore update) {
	List<Contact> contactsToUpdate = new List<Contact>();
	boolean meetsConditions = false;
	for(Contact c: trigger.new) {
		if(meetsConditions) { contactsToUpdate.add(c); }
	}
	EmailIssueRemover.removeEmailIssues(contactsToUpdate);
}

 

And the removeEmailIssues() method would loop through the Contact List, make a single callout with something like a comma-delimited list of the Contact IDs to work with.

Damien_Damien_

I have one minor adjustment to make ot craigmh suggestion.  You cannot pass Nonprimitive objects into a future method.  Get a set of Ids to do the work on and do all of your work in the future method with those ids.

 

trigger InvalidEmailContactTrigger on Contact (bef​ore update) {
	Set<Id> contactsToUpdate = new Set<Id>();
	boolean meetsConditions = false;
	for(Contact c: trigger.new) {
		if(meetsConditions) { contactsToUpdate.add(c.Id); }
	}
	EmailIssueRemover.removeEmailIssues(contactsToUpdate);
}
This was selected as the best answer
craigmhcraigmh

Thanks for catching that. I love helping and learning at the same time.

nelrib88nelrib88

thanks so much for the help guys.