1

I'm trying to bulk upload 20,000 Locations (Location__c) which are cities with additional information such as state, country, lat, long, etc. When I try to upload them, I get the following error message, "LocationTrigger: System.LimitException: Too many future calls: 51 Status code: 16"

I've pasted the trigger below. Could someone please tell me what I should change to remove this limit? I'm new to Triggers, so all help is appreciated. Thank you so much!

trigger LocationTrigger on Location__c (after insert, after update) {

for (Location__c l : trigger.new)
{
    if(trigger.IsAfter)
    {
        /*
        * After Insert - Geocode location address if address fields are not null
        *
        */
        if(trigger.isInsert)
        {
            if (l.Primary_Location__Latitude__s == null && l.City__c!=null && l.ZIP_Code__c!=null && l.State__c!=null)
            {
                 LocationCallouts.getLocationGeocode(l.id);
            }
        }
        /*
        * After Update - Geocode location address if address fields have changed and are not null
        *
        */
        if(trigger.isUpdate)
        {
            Location__c oldloc = Trigger.oldMap.get(l.Id);
            if ((oldloc.City__c!=l.City__c || oldloc.ZIP_Code__c!=l.ZIP_Code__c || oldloc.State__c!=l.State__c) && l.City__c!=null && l.ZIP_Code__c!=null && l.State__c!=null)
            {
                 LocationCallouts.getLocationGeocode(l.id);
            }
        }

    }
}

And here is the Class (updated post to include this):

public class LocationCallouts { 

//LocationCallouts.getLocationGeocode('a03M0000005SCKE');
 @future (callout=true)  // future method needed to run callouts from Triggers
  static public void getLocationGeocode(id locID){
    // gather account info
    Location__c l = [SELECT City__c,Country__c,ZIP_Code__c,State__c FROM Location__c WHERE id =: locID];

    // create an address string
    String address = '';
    //if (a.BillingStreet != null)
    //    address += a.BillingStreet +', ';
    if (l.City__c != null)
        address += l.City__c +', ';
    if (l.State__c != null)
        address += l.State__c +' ';
    if (l.ZIP_Code__c != null)
        address += l.ZIP_Code__c +', ';
    if (l.Country__c != null)
        address += l.Country__c;

    system.debug('address = ' + address);
    address = EncodingUtil.urlEncode(address, 'UTF-8');

    // build callout
    Http h = new Http();
    HttpRequest req = new HttpRequest();
    String APIKEY = 'AIzaSyDIC7Hww3ArSvrU9u6JasSnZzjr19OPBw4';
    req.setEndpoint('https://maps.googleapis.com/maps/api/geocode/json?address='+address+'&key='+APIKEY);
    req.setMethod('GET');
    req.setTimeout(60000);

    try{
        // callout
        HttpResponse res = h.send(req);

        // parse coordinates from response
        JSONParser parser = JSON.createParser(res.getBody());
        system.debug('res.getBody() = ' + res.getBody());
        double lat = null;
        double lon = null;
        while (parser.nextToken() != null) {
            if ((parser.getCurrentToken() == JSONToken.FIELD_NAME) &&
                (parser.getText() == 'location' || Test.IsRunningTest())){
                   parser.nextToken(); // object start
                   while (parser.nextToken() != JSONToken.END_OBJECT){
                       String txt = parser.getText();
                       parser.nextToken();
                       if (txt == 'lat')
                       {
                           lat = parser.getDoubleValue();
                           system.debug('lat = ' + lat);
                       }
                       else if (txt == 'lng')
                       {
                           lon = parser.getDoubleValue();
                           system.debug('lon = ' + lon);
                       }
                   }

            }
        }

        // update coordinates if we get back
        if (lat != null){
            l.Primary_Location__Latitude__s = lat;
            l.Primary_Location__Longitude__s = lon;
            system.debug('l.Primary_Location__Latitude__s ' + l.Primary_Location__Latitude__s);
            system.debug('l.Primary_Location__Longitude__s ' + l.Primary_Location__Longitude__s);
            update l;
        }

    } catch (Exception e) {
        system.debug('Exception = ' + e);
    }
}

 @future (callout=true)  // future method needed to run callouts from Triggers
  static public void getProviderGeocode(id proID){
    // gather account info
    Provider__c p = [SELECT Physical_Address_1__c,Physical_City__c,Physical_State__c, Physical_ZIP_Code__c, Geolocation__c FROM Provider__c WHERE id =: proID];

    // create an address string
    String address = '';

    if (p.Physical_Address_1__c != null)
        address += p.Physical_Address_1__c +',';
    if (p.Physical_City__c != null)
        address += p.Physical_City__c +',';
    if (p.Physical_State__c != null)
        address += p.Physical_State__c +' ';
    if (p.Physical_ZIP_Code__c != null)
        address += p.Physical_ZIP_Code__c +',';
   /*
    if (p.Country__c != null)
        address += p.Country__c;
    */
    system.debug('address = ' + address);
    address = EncodingUtil.urlEncode(address, 'UTF-8');

    // build callout
    Http h = new Http();
    HttpRequest req = new HttpRequest();
    String APIKEY = 'AIzaSyDIC7Hww3ArSvrU9u6JasSnZzjr19OPBw4';
    req.setEndpoint('https://maps.googleapis.com/maps/api/geocode/json?address='+address+'&key='+APIKEY);
    req.setMethod('GET');
    req.setTimeout(60000);

    try{
        // callout
        HttpResponse res = h.send(req);

        // parse coordinates from response
        JSONParser parser = JSON.createParser(res.getBody());
        system.debug('res.getBody() = ' + res.getBody());
        double lat = null;
        double lon = null;
        while (parser.nextToken() != null) {
            if ((parser.getCurrentToken() == JSONToken.FIELD_NAME) &&
                (parser.getText() == 'location')){
                   parser.nextToken(); // object start
                   while (parser.nextToken() != JSONToken.END_OBJECT){
                       String txt = parser.getText();
                       parser.nextToken();
                       if (txt == 'lat')
                       {
                           lat = parser.getDoubleValue();
                           system.debug('lat = ' + lat);
                       }
                       else if (txt == 'lng')
                       {
                           lon = parser.getDoubleValue();
                           system.debug('lon = ' + lon);
                       }
                   }

            }
        }

        // update coordinates if we get back
        if (lat != null){
            p.Geolocation__Latitude__s = lat;
            p.Geolocation__Longitude__s = lon;
            system.debug('p.Geolocation__Latitude__s ' + p.Geolocation__Latitude__s);
            system.debug('p.Geolocation__Longitude__s ' + p.Geolocation__Longitude__s);
            update p;
        }

    } catch (Exception e) {
        system.debug('Exception = ' + e);
    }
}

}

  • 1
    While you have posted the code for the trigger, the source of the exception is in the class LocationCallouts...please post this class as well. Also refer to the platform governor limits, as I suspect that the LocationCallouts.getLocationGeocode is making a future call per record in the trigger context, and as the message describes, you're limited to 50 in a single transaction - which is considerably less than 20K :) – Brian Mansfield Nov 10 '15 at 17:18
  • @BrianMansfield the 20k is irrelevant; the system will call the code in batches of 200. – sfdcfox Nov 10 '15 at 18:05
  • 1
    It would likely help though if he also bulkified his trigger and class so that he can send the callouts to the class in bulk. The number of future callouts that can result from it in a single execution context is limited to 50 which is far less than the 200 a trigger can process – crmprogdev Nov 10 '15 at 18:08
2

You need to batch up the future calls into more manageable chunks. Since you can have up to 100 callouts per transaction, you can do as follows. Also, I've consolidated your code for you:

trigger LocationTrigger on Location__c (after insert, after update) {
    Id[] recordIds = new Id[0];
    for(Integer index = 0, size = Trigger.new.size(); index < size; index++) {
        Location__c record = Trigger.new[index], 
                    oldRecord = Trigger.isInsert? null: Trigger.old[index];
        Boolean canUpdate = record.City__c!=null && record.ZIP_Code__c!=null && record.State__c!=null,
                hasChanged = Trigger.isInsert || oldRecord.City__c != record.City__c || || oldRecord.ZIP_Code__c != record.ZIP_Code__c || oldRecord.State__c != record.State__c;
        if(canUpdate && hasChanged) {
            recordIds.add(record.Id);
        }
        if(recordIds.size() == 100 || (index == size - 1 && index > 0)) {
            LocationCallouts.getGeoCodeBatch(recordIds);
            recordIds.clear();
        }
    }
}

If you keep running into callout timeout exceptions, you might use a smaller value, like 10 or 20 instead of 100.

  • if he's using Data Loader, couldn't he just reduce the batch size to < 200 and avoid recoding anything? – cropredy Nov 10 '15 at 18:23
  • @crop1645 Yes, they could reduce the batch size to say, 50 or 25. However, that's only a temporary solution to a permanent problem. – sfdcfox Nov 10 '15 at 18:25
  • ah yes, the balance between one-time work and 'doing it right' -- I know you are always in the latter camp :-) – cropredy Nov 10 '15 at 18:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.