[SalesForce] FATAL_ERROR|System.LimitException: Too many callouts: 101

I am trying to do a batch update that enters geocoordinates for 2000 records of a cuctom object called Property__c without gecoordinates using the following code:

Batch

        global class batchUpdateAccountGeoLocation implements Database.Batchable<sObject>,Database.AllowsCallouts{

      //Start 
       global Database.QueryLocator start(Database.BatchableContext BC){
           string query = 'SELECT Id, Name, Property_Address__c,City__c,State__c,GeoCoord__Latitude__s,GeoCoord__Longitude__s FROM Property__c WHERE Property_Address__c!=null AND GeoCoord__Longitude__s  = null';
          system.debug('query::'+query);
          return Database.getQueryLocator(query);
       }

        //Execute
       global void execute(Database.BatchableContext BC,List<Property__c> scope){

          for(Property__c a : scope){          
                        GeoCodeResponse ogc = GeoCodeAddress.getGeoCodeAddress(a.Property_Address__c.replace(' ','+') + ',' + a.City__c + ',' + a.State__c);
                         if(ogc.status!='ZERO_RESULTS')
                         {
                         a.GeoCoord__Latitude__s = ogc.results[0].geometry.location.lat;
                         a.GeoCoord__Longitude__s = ogc.results[0].geometry.location.lng;
                         }                  
          }     

           try{
           system.debug('scope::'+scope);
               update scope;
           }catch(exception e){
               system.debug('batchUpdateAccountGeoLocation exception:' + e.getMessage());
           }       
       }

        //Finish
       global void finish(Database.BatchableContext BC){

       }
    }

Related Class

public class GeoCodeAddress {    

    public static GeoCodeResponse getGeoCodeAddress(String Address) {
        string url ='https://maps.googleapis.com/maps/api/geocode/json?key=AIzaSyAa-MPRtAoFmdqnSJJIjRvZ7_lcKwwt8oI&address=' + EncodingUtil.urlEncode(Address, 'UTF-8');

        // Instantiate a new http object
        Http h = new Http();

        // Instantiate a new HTTP request, specify the method (GET) as well as the endpoint
        HttpRequest req = new HttpRequest();
        req.setEndpoint(url);
        req.setMethod('GET');

        // Send the request, and return a response
        HttpResponse res = h.send(req);        
        system.debug('res::'+res.getBody());
        return GeoCodeResponse.parse(res.getBody());         

    }

}

public class GeoCodeResponse {

  public class Address_components {
    public String long_name;
    public String short_name;
    public List<String> types;
  }

  public List<Results> results;
  public String status;

  public class Location {
    public Double lat;
    public Double lng;
  }

  public class Geometry {
    public Location location;
    public String location_type;
    public Viewport viewport;
  }

  public class Results {
    public List<Address_components> address_components;
    public String formatted_address;
    public Geometry geometry;
    public List<String> types;
  }

  public class Viewport {
    public Location northeast;
    public Location southwest;
  }

  public static GeoCodeResponse parse(String json) {
    return (GeoCodeResponse) System.JSON.deserialize(json, GeoCodeResponse.class);
  }
   }

However, when I run this, I get FATAL_ERROR|System.LimitException: Too many callouts: 101

I know that best practices is to use lists to do these sort of updates, and not execute them in a for loop, but I thought I was already doing that? My for loop does not update anything or make any soql calls. I also do not have any triggers on my Property object.

This is my first time using this sort of batch update so I hope that this is a simple error on my part. I see there are many similar questions, but I was not able to find a straight answer to my problem in any of them. Hopefully I do not get downvoted for this.

EDIT
I changed my execute anonymous statement from

batchUpdateAccountGeoLocation c = new batchUpdateAccountGeoLocation();
Database.executeBatch(c,2000);

to

batchUpdateAccountGeoLocation c = new batchUpdateAccountGeoLocation();
Database.executeBatch(c,100);

Thinking that next I would attempt to update my records in several 100-record chunks and perhaps this would work. To my surprise, it seems to have updated all 2000 of my records. Not sure why this happened, but all my records now have geocoords.

Best Answer

The limit you report in your question is:

FATAL_ERROR|System.LimitException: Too many callouts: 101

meaning that a transaction has attempted to make more than the allowed 100 callouts.

Your execute done like this:

batchUpdateAccountGeoLocation c = new batchUpdateAccountGeoLocation();
Database.executeBatch(c,100);

is exactly the right solution because it ensures a maximum of 100 records is passed the the execute method (which makes one web service callout per record). The processing may have taken place in 20 execute calls, but thats one of the points of this mechanism: it breaks the work up into defined size blocks that are each processed in their own transaction and so each get their own set of governor limits.

PS

The documentation is misleading: you don't need to use global in your batchable and you can use compile-time checked static SOQL:

public Database.QueryLocator start(Database.BatchableContext bc) {
    return Database.getQueryLocator([
            SELECT Id, Property_Address__c, City__c, State__c
            FROM Property__c
            WHERE Property_Address__c != null
            AND GeoCoord__Longitude__s = null
            ]);
}
Related Topic