But batch Apex does not allow you to directly invoke batch Apex to start another job due to governor limits. How can we ensure that a second batch process only executes after the first batch process completes successfully? There is more than one way to solve this problem. For this case I am going to show you how to resolve this using Apex scheduling. The pattern looks like the following:
- Create batch Apex 1.
- Create batch Apex 2.
- Create schedulable Apex.
- Inside finish() method of batch Apex 1, invoke the schedule Apex with an execution time of now().
- Inside the schedule Apex, execute batch Apex 2.
Now to share some quick sample code. This code below doesn't do anything valuable other than show this pattern.
BATCH APEX 1 - The first batch Apex process.
global class AccountBatch1 implements Database.Batchable<sobject>
{
global final String Query;
global final String Entity;
global final String Field;
global final String Value;
global AccountBatch1(String q)
{
Query=q;
}
global Database.QueryLocator start(Database.BatchableContext BC)
{
return Database.getQueryLocator(query);
}
global void execute(Database.BatchableContext BC,
List<sObject> scope)
{
List<Account> updateAccts = new List<Account>();
for(Sobject s : scope)
{
Account a = (Account) s;
a.Name = a.Name + 'Batch 1.';
}
update updateAccts;
}
//The batch process has completed successfully. Schedule next batch.
global void finish(Database.BatchableContext BC)
{
System.debug(LoggingLevel.WARN,'Batch Process 1 Finished');
//Build the system time of now + 20 seconds to schedule the batch apex.
Datetime sysTime = System.now();
sysTime = sysTime.addSeconds(20);
String chron_exp = '' + sysTime.second() + ' ' + sysTime.minute() + ' ' + sysTime.hour() + ' ' + sysTime.day() + ' ' + sysTime.month() + ' ? ' + sysTime.year();
system.debug(chron_exp);
AccountBatch2Scheduler acctBatch2Sched = new AccountBatch2Scheduler();
//Schedule the next job, and give it the system time so name is unique
System.schedule('acctBatch2Job' + sysTime.getTime(),chron_exp,acctBatch2Sched);
}
}
BATCH APEX 2 - The second batch Apex process.
global class AccountBatch2 implements Database.Batchable<sobject>
{
global final String Query;
global final String Entity;
global final String Field;
global final String Value;
global AccountBatch2(String q)
{
Query=q;
}
global Database.QueryLocator start(Database.BatchableContext BC)
{
return Database.getQueryLocator(query);
}
global void execute(Database.BatchableContext BC,List<sObject> scope)
{
List<Account> updateAccts = new List<Account>();
for(Sobject s : scope)
{
Account a = (Account) s;
a.Name = a.Name + 'Batch 1.';
}
update updateAccts;
}
global void finish(Database.BatchableContext BC)
{
System.debug(LoggingLevel.WARN,'Batch Process 2 Finished');
}
}
SCHEDULE APEX - Just execute the next batch Apex.
global class AccountBatch2Scheduler implements Schedulable
{ global void execute(SchedulableContext ctx)
{
AccountBatch2 acctb2 = new AccountBatch2('Select Id, Name from Account');
ID batchprocessid = Database.executeBatch(acctb2,20);
}
}
Leveraging these classes, you can continually execute this batch sequence. I have included some of the monitoring logs to show the output of this process.
Here you can see the batch Jobs were executed:
Here you can see the scheduled Apex for executing the second batch:
This is not the only solution to the problem, but it is one that I personally prefer. You can also use Salesforce Email Services if you so desire, however I prefer this approach.
I have an idea posted on the Salesforce Idea Exchange to allow Batch Apex to call another Batch Apex directly from the finish() method. You can vote for it here: Batch Apex invoke from Finish Method of another Batch Apex.

