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
BBeairdBBeaird 

Scheduling Apex Batch with Callouts-Future method cannot be called/uncommited work/too many callouts

I have a class which does HTTP callouts.  Because I am needing to do more than 10 callouts at a time, I am using a Batch class to call that class.  At this point, I can call that class without issue by directly calling the batch execute via system log.  The next step I need to do is put the Batch in a scheduler that runs every hour.  So I now have 4 classes:

 

1.) My original class that does the HTTP callouts

2.) The batch class which calls #1 scoped one record at a time to avoid the 10-callout limit

3.) A schedulable class which executes the batch class and has a method to invoke system.scheduler

 

But now I'm stuck because it seems no matter how I organize things, I get one of these errors:

 

1.) Future method cannot be called from a future method (I'm getting this one with my current code setup)

2.) You have uncommitted work pending (I get this one if the callout method isn't a future method)

3.) Too many callouts - 2 (I get this one if I make the callout method NOT a future method and somehow try to update the assets all together in the batch class)

 

What the heck am I supposed to do to get this class scheduled??  Below is my code.

 

Class #1: The HTTP Callout Class

global class VLStatus {

 

  @future(callout=true)
   global static void getMachineStatus(String asstId){

        Asset asst = new Asset();
        asst = [select id, name, SerialNumber from Asset where Id = :asstId];

 

       //code that does my callout stuff and sets the asset fields

      update asset;

  }

 

Class #2: The Batch Class

global class VLStatusBatch implements Database.Batchable<Asset>, Database.AllowsCallouts{

//Get list of machines that we are still waiting on for a response
Asset[] AssetsToUpdate = [Select id, name, SerialNumber from Asset where Pending_VL_Response__c = 1];

global Iterable<Asset> start(database.batchablecontext BC){
    return (AssetsToUpdate);    
}

global void execute(Database.BatchableContext BC, List<Asset> scope){
    for(Asset a : scope){
            VLStatus.getMachineStatus(a.Id);
    }    
}

global void finish(Database.BatchableContext info){
    }//global void finish loop

}

 

 

 

Class #3: The Schedulable Class

global class HourlyScheduler implements Schedulable{
    global HourlyScheduler (){}

    public static void start(){
        //Seconds Minutes Hours Day_of_month Month Day_of_week optional_year
        System.schedule('Pending Machine Status Update', '0 5 1-23 * * ?', New HourlyScheduler());

    }

    global void execute(SchedulableContext ctx){
        VLStatusBatch b = new VLStatusBatch();
        database.executebatch(b);    
    }
}

 

Best Answer chosen by Admin (Salesforce Developers) 
BBeairdBBeaird

I actually found my problem.  I was so close.

 

First, you can't call a future method from a Batch class.  My guess is that the threads generated from batches are considered "future" methods, so trying to do it results in the "Future method cannot be called from a future method" error. So, I removed the future designation from the HTTP callout class. 

 

But what got it for me was making sure to pass the optional scope parameter when my schedulable class calls the batch.  Otherwise I hit the wall for callout limits. 

 

So this:

database.executebatch(b);   

 

Becomes:

database.executebatch(b, 1);   

 

And now the scheduled job runs perfectly, just as if I had manually run it myself.

All Answers

BBeairdBBeaird

I actually found my problem.  I was so close.

 

First, you can't call a future method from a Batch class.  My guess is that the threads generated from batches are considered "future" methods, so trying to do it results in the "Future method cannot be called from a future method" error. So, I removed the future designation from the HTTP callout class. 

 

But what got it for me was making sure to pass the optional scope parameter when my schedulable class calls the batch.  Otherwise I hit the wall for callout limits. 

 

So this:

database.executebatch(b);   

 

Becomes:

database.executebatch(b, 1);   

 

And now the scheduled job runs perfectly, just as if I had manually run it myself.

This was selected as the best answer
DHO_604DHO_604

Thank you BBeaird, this is an excellent solution to what I'm doing.  It's 10x better than the SFDC documentation

BBeairdBBeaird

Seeing as it's timely, I thought I would add an update to this. In summer '12, Salesforce is increasing the callout limit in a batch to 10. This means I can bump my batch size from 1 to 10, which is nice. It also means I can drop an e-mail service I was using in another class as a way to "chain" batch jobs where I needed to do a couple callouts for one record.

 

One other caviat here is that I had to update the Execute method to add each asset to a list and then do an update on the list at the end of the loop rather than updating each asset individually. If you don't change that, you'll get the "uncommitted work" error. Plus, it's just good to keep DML actions out of loops wherever possible.

Prak088Prak088

BBeairdBBeaird

@Prak088: I have since combined my scheduler and executions classes into one and updated my code above.

 

You are right - the final step is to actually start the scheduler so it will run once an hour. The way to do this is to open up an execute anonymous window through either eclipse or the developer console and run this:

 

hourlyScheduler.start();

Prak088Prak088

Hi could you post the code for the combined class as well as the test class for it


BBeairdBBeaird

The new combined class is below. To be honest, it's somewhat watered down from my actual code so it can be clearer, so trying to post the test class won't do you much good unless I modify it to be readable by external eyes. I don't have time for that the moment, but I'll see what I can do over the next week or so.

 

Class #3: The Schedulable Class

global class HourlyScheduler implements Schedulable{
    global HourlyScheduler (){}

    public static void start(){
        //Seconds Minutes Hours Day_of_month Month Day_of_week optional_year
        System.schedule('Pending Machine Status Update', '0 5 1-23 * * ?', New HourlyScheduler());

    }

    global void execute(SchedulableContext ctx){
        VLStatusBatch b = new VLStatusBatch();
        database.executebatch(b);    
    }
}

Prak088Prak088

@