Use Batch Apex

To use batch Apex, write an Apex class that implements the Salesforce-provided interface Database.Batchable and then invoke the class programmatically. To monitor or stop the execution of the batch Apex job, from Setup, enter Apex Jobs in the Quick Find box and then select Apex Jobs.

Implement the Database.Batchable Interface

The Database.Batchable interface contains three methods that must be implemented.

  • start method:
    1public (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {}

    The start method is called at the beginning of a batch Apex job. In the start method, you can include code that collects records or objects to pass to the interface method execute. This method returns either a Database.QueryLocator object or an iterable that contains the records or objects passed to the job.

    When you’re using a simple query (SELECT) to generate the scope of objects in the batch job, use the Database.QueryLocator object. If you use a QueryLocator object, the governor limit for the total number of records retrieved by SOQL queries is bypassed. For example, a batch Apex job for the Account object can return a QueryLocator for all account records (up to 50 million records) in an org. Another example is a sharing recalculation for the Contact object that returns a QueryLocator for all account records in an org.

    Use the iterable to create a complex scope for the batch job. You can also use the iterable to create your own custom process for iterating through the list.

    If you use an iterable, the governor limit for the total number of records retrieved by SOQL queries is still enforced. For more information on using iterables for batch jobs, see Batch Apex Considerations and Best Practices.

    Important

  • execute method:
    1public void execute(Database.BatchableContext bc, list<P>){}

    The execute method is called for each batch of records that you pass to it and takes these parameters.

    • A reference to the Database.BatchableContext object.
    • A list of sObjects, such as List<sObject>, or a list of parameterized types. If you’re using a Database.QueryLocator, use the returned list.

    Batches of records tend to execute in the order in which they’re received from the start method. However, the order in which batches of records execute depends on various factors. The order of execution isn’t guaranteed.

  • finish method:
    1public void finish(Database.BatchableContext bc){}

    The finish method is called after all batches are processed and can be used to send confirmation emails or execute post-processing operations.

Each execution of a batch Apex job is considered a discrete transaction. For example, a batch Apex job that contains 1,000 records and is executed without the optional scope parameter from Database.executeBatch is considered five transactions of 200 records each. The Apex governor limits are reset for each transaction. If the first transaction succeeds but the second fails, the database updates made in the first transaction aren’t rolled back.

Use Database.BatchableContext

All the methods in the Database.Batchable interface require a reference to a Database.BatchableContext object. Use this object to track the progress of the batch job.

The following is the instance method with the Database.BatchableContext object:

Name Arguments Returns Description
getJobID ID Returns the ID of the AsyncApexJob object associated with this batch job as a string. Use this method to track the progress of records in the batch job. You can also use this ID with the System.abortJob method.

The following example uses the Database.BatchableContext to query the AsyncApexJob associated with the batch job.

1public void finish(Database.BatchableContext bc){
2   // Get the ID of the AsyncApexJob representing this batch job
3   // from Database.BatchableContext.
4   // Query the AsyncApexJob object to retrieve the current job's information.
5   AsyncApexJob a = [SELECT Id, Status, NumberOfErrors, JobItemsProcessed,
6      TotalJobItems, CreatedBy.Email
7      FROM AsyncApexJob WHERE Id =
8      :bc.getJobId()];
9   // Send an email to the Apex job's submitter notifying of job completion.
10   Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
11   String[] toAddresses = new String[] {a.CreatedBy.Email};
12   mail.setToAddresses(toAddresses);
13   mail.setSubject('Apex Sharing Recalculation ' + a.Status);
14   mail.setPlainTextBody
15   ('The batch Apex job processed ' + a.TotalJobItems +
16   ' batches with '+ a.NumberOfErrors + ' failures.');
17   Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
18}

Using Database.QueryLocator to Define Scope

The start method can return either a Database.QueryLocator object that contains the records to use in the batch job or an iterable.

The following example uses a Database.QueryLocator:
1public class SearchAndReplace implements Database.Batchable<sObject>{
2
3   public final String Query;
4   public final String Entity;
5   public final String Field;
6   public final String Value;
7
8   public SearchAndReplace(String q, String e, String f, String v){
9
10      Query=q; Entity=e; Field=f;Value=v;
11   }
12
13   public Database.QueryLocator start(Database.BatchableContext bc){
14      return Database.getQueryLocator(query);
15   }
16
17   public void execute(Database.BatchableContext bc, List<sObject> scope){
18     for(sobject s : scope){
19     s.put(Field,Value); 
20     }
21     update scope;
22    }
23
24   public void finish(Database.BatchableContext bc){
25   }
26}

Using an Iterable in Batch Apex to Define Scope

The start method can return either a Database.QueryLocator object that contains the records to use in the batch job or an iterable. Use an iterable to step through the returned items more easily.

1public class batchClass implements Database.batchable{ 
2   public Iterable start(Database.BatchableContext info){ 
3       return new CustomAccountIterable(); 
4   }     
5   public void execute(Database.BatchableContext info, List<Account> scope){
6       List<Account> accsToUpdate = new List<Account>();
7       for(Account a : scope){ 
8           a.Name = 'true'; 
9           a.NumberOfEmployees = 70; 
10           accsToUpdate.add(a); 
11       } 
12       update accsToUpdate; 
13   }     
14   public void finish(Database.BatchableContext info){     
15   } 
16}

Using the Database.executeBatch Method to Submit Batch Jobs

You can use the Database.executeBatch method to programmatically begin a batch job.

When you call Database.executeBatch, Salesforce adds the process to the queue. Actual execution can be delayed based on service availability.

Important

The Database.executeBatch method takes two parameters:

  • An instance of a class that implements the Database.Batchable interface.
  • An optional parameter scope. This parameter specifies the number of records to pass into the execute method. Use this parameter when you have many operations for each record being passed in and are running into governor limits. By limiting the number of records, you’re limiting the operations per transaction. This value must be greater than zero. If the start method of the batch class returns a QueryLocator, the optional scope parameter of Database.executeBatch can have a maximum value of 2,000. If set to a higher value, Salesforce chunks the records returned by the QueryLocator into smaller batches of up to records. If the start method of the batch class returns an iterable, the scope parameter value has no upper limit. However, if you use a high number, you can run into other limits. The optimal scope size is a factor of 2000, for example, 100, 200, 400 and so on.
The Database.executeBatch method returns the ID of the AsyncApexJob object, which you can use to track the progress of the job. For example:
1ID batchprocessid = Database.executeBatch(reassign);
2
3AsyncApexJob aaj = [SELECT Id, Status, JobItemsProcessed, TotalJobItems, NumberOfErrors 
4                    FROM AsyncApexJob WHERE ID =: batchprocessid ];

You can also use this ID with the System.abortJob method.

For more information, see AsyncApexJob in the Object Reference for Salesforce.

Holding Batch Jobs in the Apex Flex Queue

With the Apex flex queue, you can submit up to 100 batch jobs.

The outcome of Database.executeBatch is as follows.
  • The batch job is placed in the Apex flex queue, and its status is set to Holding.
  • If the Apex flex queue has the maximum number of 100 jobs, Database.executeBatch throws a LimitException and doesn't add the job to the queue.
  • If your org doesn’t have Apex flex queue enabled, Database.executeBatch adds the batch job to the batch job queue with the Queued status. If the concurrent limit of queued or active batch jobs has been reached, a LimitException is thrown, and the job isn’t queued.
  • It is possible that the number of jobs in the Apex flex queue sometimes exceeds the maximum limit, resulting from parallel requests to enqueue batch Apex jobs. Further attempts to enqueue batch jobs will encounter a LimitException until the queue size drops below the maximum limit.

Reordering Jobs in the Apex Flex Queue

While submitted jobs have a status of Holding, you can reorder them in the Salesforce user interface to control which batch jobs are processed first. To do so, from Setup, enter Apex Flex Queue in the Quick Find box, then select Apex Flex Queue.

Alternatively, you can use Apex methods to reorder batch jobs in the flex queue. To move a job to a new position, call one of the System.FlexQueue methods. Pass the method the job ID and, if applicable, the ID of the job next to the moved job’s new position. For example:

1Boolean isSuccess = System.FlexQueue.moveBeforeJob(jobToMoveId, jobInQueueId);
You can reorder jobs in the Apex flex queue to prioritize jobs. For example, you can move a batch job up to the first position in the holding queue to be processed first when resources become available. Otherwise, jobs are processed “first-in, first-out”—in the order in which they’re submitted.

When system resources become available, the system picks up the next job from the top of the Apex flex queue and moves it to the batch job queue. The system can process up to five queued or active jobs simultaneously for each organization. The status of these moved jobs changes from Holding to Queued. Queued jobs get executed when the system is ready to process new jobs. You can monitor queued jobs on the Apex Jobs page.

Batch Job Statuses

The following table lists all possible statuses for a batch job along with a description of each.

Status Description
Holding Job has been submitted and is held in the Apex flex queue until system resources become available to queue the job for processing.
Queued Job is awaiting execution.
Preparing The start method of the job has been invoked. This status can last a few minutes depending on the size of the batch of records.
Processing Job is being processed.
Aborted Job aborted by a user.
Completed Job completed with or without failure.
Failed Job experienced a system failure.

Using the System.scheduleBatch Method

You can use the System.scheduleBatch method to schedule a batch job to run once at a future time.

The System.scheduleBatch method takes these parameters.
  • An instance of a class that implements the Database.Batchable interface.
  • The job name.
  • The time interval, in minutes, after which the job starts executing.
  • An optional scope value. This parameter specifies the number of records to pass into the execute method. Use this parameter when you have many operations for each record being passed in and are running into governor limits. By limiting the number of records, you’re limiting the operations per transaction. This value must be greater than zero.If the start method of the batch class returns a QueryLocator, the optional scope parameter of Database.executeBatch can have a maximum value of . If set to a higher value, Salesforce chunks the records returned by the QueryLocator into smaller batches of up to 2,000 records. If the start method of the batch class returns an iterable, the scope parameter value has no upper limit. However, if you use a high number, you can run into other limits. The optimal scope size is a factor of 2000, for example, 100, 200, 400 and so on.

The System.scheduleBatch method returns the scheduled job ID (CronTrigger ID).

This example schedules a batch job to run 60 minutes from now by calling System.scheduleBatch. The example passes this method an instance of a batch class (the reassign variable), a job name, and a time interval of 60 minutes. The optional scope parameter has been omitted. The method returns the scheduled job ID, which is used to query CronTrigger to get the status of the corresponding scheduled job.
1String cronID = System.scheduleBatch(reassign, 'job example', 60);
2
3CronTrigger ct = [SELECT Id, TimesTriggered, NextFireTime
4                FROM CronTrigger WHERE Id = :cronID];
5
6// TimesTriggered should be 0 because the job hasn't started yet.
7System.assertEquals(0, ct.TimesTriggered);
8System.debug('Next fire time: ' + ct.NextFireTime); 
9// For example:
10// Next fire time: 2013-06-03 13:31:23

For more information, see CronTrigger in the Object Reference for Salesforce.

Some things to note about System.scheduleBatch:

  • When you call System.scheduleBatch, Salesforce schedules the job for execution at the specified time. Actual execution occurs at or after that time, depending on service availability.
  • The scheduler runs as system—all classes are executed, whether the user has permission to execute the class or not.
  • When the job’s schedule is triggered, the system queues the batch job for processing. If Apex flex queue is enabled in your org, the batch job is added at the end of the flex queue. For more information, see Holding Batch Jobs in the Apex Flex Queue.
  • All scheduled Apex limits apply for batch jobs scheduled using System.scheduleBatch. After the batch job is queued (with a status of Holding or Queued), all batch job limits apply and the job no longer counts toward scheduled Apex limits.
  • After calling this method and before the batch job starts, you can use the returned scheduled job ID to abort the scheduled job using the System.abortJob method.

Note

Batch Apex Examples

The following example uses a Database.QueryLocator:
1public class UpdateAccountFields implements Database.Batchable<sObject>{
2   public final String Query;
3   public final String Entity;
4   public final String Field;
5   public final String Value;
6
7   public UpdateAccountFields(String q, String e, String f, String v){
8             Query=q; Entity=e; Field=f;Value=v;
9   }
10
11   public Database.QueryLocator start(Database.BatchableContext bc){
12      return Database.getQueryLocator(query);
13   }
14
15   public void execute(Database.BatchableContext bc, 
16                       List<sObject> scope){
17      for(Sobject s : scope){s.put(Field,Value); 
18      }      update scope;
19   }
20
21   public void finish(Database.BatchableContext bc){
22
23   }
24
25}
You can use this code to call the previous class.
1// Query for 10 accounts
2String q = 'SELECT Industry FROM Account LIMIT 10';
3String e = 'Account';
4String f = 'Industry';
5String v = 'Consulting';
6Id batchInstanceId = Database.executeBatch(new UpdateAccountFields(q,e,f,v), 5);
To exclude accounts or invoices that were deleted but are still in the Recycle Bin, include isDeleted=false in the SOQL query WHERE clause, as shown in these modified samples.
1// Query for accounts that aren't in the Recycle Bin
2String q = 'SELECT Industry FROM Account WHERE isDeleted=false LIMIT 10';
3String e = 'Account';
4String f = 'Industry';
5String v = 'Consulting';
6Id batchInstanceId = Database.executeBatch(new UpdateAccountFields(q,e,f,v), 5);
1// Query for invoices that aren't in the Recycle Bin
2String q = 
3  'SELECT Description__c FROM Invoice_Statement__c WHERE isDeleted=false LIMIT 10';
4String e = 'Invoice_Statement__c';
5String f = 'Description__c';
6String v = 'Updated description';
7Id batchInstanceId = Database.executeBatch(new UpdateInvoiceFields(q,e,f,v), 5);
The following class uses batch Apex to reassign all accounts owned by a specific user to a different user.
1public class OwnerReassignment implements Database.Batchable<sObject>{
2String query;
3String email;
4Id toUserId;
5Id fromUserId;
6
7public Database.querylocator start(Database.BatchableContext bc){
8            return Database.getQueryLocator(query);}
9
10public void execute(Database.BatchableContext bc, List<sObject> scope){
11    List<Account> accns = new List<Account>();
12
13   for(sObject s : scope){Account a = (Account)s;
14        if(a.OwnerId==fromUserId){
15            a.OwnerId=toUserId;
16            accns.add(a);
17            }
18        }
19
20update accns;
21    
22}
23public void finish(Database.BatchableContext bc){
24Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
25
26mail.setToAddresses(new String[] {email});
27mail.setReplyTo('batch@acme.com');
28mail.setSenderDisplayName('Batch Processing');
29mail.setSubject('Batch Process Completed');
30mail.setPlainTextBody('Batch Process has completed');
31
32Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
33}
34}
Use this code to execute the OwnerReassignment class in the previous example.
1OwnerReassignment reassign = new OwnerReassignment();
2reassign.query = 'SELECT Id, Name, Ownerid FROM Account ' + 
3                'WHERE ownerid=\'' + u.id + '\'';
4reassign.email='admin@acme.com';
5reassign.fromUserId = u;
6reassign.toUserId = u2;
7ID batchprocessid = Database.executeBatch(reassign);
The following is an example of a batch Apex class for deleting records.
1public class BatchDelete implements Database.Batchable<sObject> {
2   public String query;
3
4   public Database.QueryLocator start(Database.BatchableContext bc){
5      return Database.getQueryLocator(query);
6   }
7
8   public void execute(Database.BatchableContext bc, List<sObject> scope){
9      delete scope;
10      DataBase.emptyRecycleBin(scope);
11   }
12
13   public void finish(Database.BatchableContext bc){
14   }
15}
This code calls the BatchDelete batch Apex class to delete old documents. The specified query selects documents to delete for all documents that are in a specified folder and that are older than a specified date. Next, the sample invokes the batch job.
1BatchDelete BDel = new BatchDelete();
2Datetime d = Datetime.now();
3d = d.addDays(-1);
4// Replace this value with the folder ID that contains
5// the documents to delete.
6String folderId = '00lD000000116lD';
7// Query for selecting the documents to delete
8BDel.query = 'SELECT Id FROM Document WHERE FolderId=\'' + folderId + 
9    '\' AND CreatedDate < '+d.format('yyyy-MM-dd')+'T'+
10    d.format('HH:mm')+':00.000Z';
11// Invoke the batch job.
12ID batchprocessid = Database.executeBatch(BDel);
13System.debug('Returned batch process ID: ' + batchProcessId);

Using Callouts in Batch Apex

To use a callout in batch Apex, specify Database.AllowsCallouts in the class definition. For example:
1public class SearchAndReplace implements Database.Batchable<sObject>, 
2   Database.AllowsCallouts{
3}

Callouts include HTTP requests and methods defined with the webservice keyword.

Using State in Batch Apex

Each execution of a batch Apex job is considered a discrete transaction. For example, a batch Apex job that contains 1,000 records and is executed without the optional scope parameter is considered five transactions of 200 records each.

If you specify Database.Stateful in the class definition, you can maintain state across these transactions. When using Database.Stateful, only instance member variables retain their values between transactions. Static member variables don’t retain their values and are reset between transactions. Maintaining state is useful for counting or summarizing records as they’re processed. For example, suppose your job processes opportunity records. You can define a method in execute to aggregate the totals of the opportunity amounts as they are processed.

If you don’t specify Database.Stateful, all static and instance member variables are set back to their original values.

The following example summarizes a custom field total__c as the records are processed.
1public class SummarizeAccountTotal implements 
2    Database.Batchable<sObject>, Database.Stateful{
3
4   public final String Query;
5   public integer Summary;
6  
7   public SummarizeAccountTotal(String q){Query=q;
8     Summary = 0;
9   }
10
11   public Database.QueryLocator start(Database.BatchableContext bc){
12      return Database.getQueryLocator(query);
13   }
14   
15   public void execute(
16                Database.BatchableContext bc, 
17                List<sObject> scope){
18      for(sObject s : scope){
19         Summary = Integer.valueOf(s.get('total__c'))+Summary;
20      }
21   }
22
23public void finish(Database.BatchableContext bc){
24   }
25}
In addition, you can specify a variable to access the initial state of the class. You can use this variable to share the initial state with all instances of the Database.Batchable methods. For example:
1// Implement the interface using a list of Account sObjects
2// Note that the initialState variable is declared as final
3
4public class MyBatchable implements Database.Batchable<sObject> {
5  private final String initialState;
6  String query;
7  
8  public MyBatchable(String intialState) {
9    this.initialState = initialState;
10  }
11
12  public Database.QueryLocator start(Database.BatchableContext bc) {
13    // Access initialState here 
14    
15    return Database.getQueryLocator(query);
16  }
17
18  public void execute(Database.BatchableContext bc, 
19                      List<sObject> batch) {
20    // Access initialState here 
21    
22  }
23
24  public void finish(Database.BatchableContext bc) {
25    // Access initialState here 
26    
27  }
28}

The initialState stores only the initial state of the class. You can’t use it to pass information between instances of the class during execution of the batch job. For example, if you change the value of initialState in execute, the second chunk of processed records can’t access the new value. Only the initial value is accessible.

Testing Batch Apex

When testing your batch Apex, you can test only one execution of the execute method. Use the scope parameter of the executeBatch method to limit the number of records passed into the execute method to ensure that you aren’t running into governor limits.

The executeBatch method starts an asynchronous process. When you test batch Apex, make certain that the asynchronously processed batch job is finished before testing against the results. Use the Test methods startTest and stopTest around the executeBatch method to ensure that it finishes before continuing your test. All asynchronous calls made after the startTest method are collected by the system. When stopTest is executed, all asynchronous processes are run synchronously. If you don’t include the executeBatch method within the startTest and stopTest methods, the batch job executes at the end of your test method. This execution order applies for Apex saved using API version 25.0 and later, but not for earlier versions.

For Apex saved using API version 22.0 and later, exceptions that occur during the execution of a batch Apex job invoked by a test method are passed to the calling test method. As a result, these exceptions cause the test method to fail. If you want to handle exceptions in the test method, enclose the code in try and catch statements. Place the catch block after the stopTest method. However, with Apex saved using Apex version 21.0 and earlier, such exceptions don’t get passed to the test method and don’t cause test methods to fail.

Asynchronous calls, such as @future or executeBatch, called in a startTest, stopTest block, don’t count against your limits for the number of queued jobs.

Note

The following example tests the OwnerReassignment class.
1public static testMethod void testBatch() {
2   user u = [SELECT ID, UserName FROM User 
3             WHERE username='testuser1@acme.com'];
4   user u2 = [SELECT ID, UserName FROM User 
5              WHERE username='testuser2@acme.com'];
6   String u2id = u2.id;
7// Create 200 test accounts - this simulates one execute.  
8// Important - the Salesforce test framework only allows you to 
9// test one execute.  
10
11   List <Account> accns = new List<Account>();
12      for(integer i = 0; i<200; i++){
13         Account a = new Account(Name='testAccount'+ i, 
14                     Ownerid = u.ID); 
15         accns.add(a);
16      }
17   
18   insert accns;
19   
20   Test.StartTest();
21   OwnerReassignment reassign = new OwnerReassignment();
22   reassign.query='SELECT ID, Name, Ownerid ' +
23            'FROM Account ' +
24            'WHERE OwnerId=\'' + u.Id + '\'' +
25            ' LIMIT 200';
26   reassign.email='admin@acme.com';
27   reassign.fromUserId = u.Id;
28   reassign.toUserId = u2.Id;
29   ID batchprocessid = Database.executeBatch(reassign);
30   Test.StopTest();
31
32   System.AssertEquals(
33           database.countquery('SELECT COUNT()'
34              +' FROM Account WHERE OwnerId=\'' + u2.Id + '\''),
35           200);  
36   
37   }
38}

Use the System.Test.enqueueBatchJobs and System.Test.getFlexQueueOrder methods to enqueue and reorder no-operation jobs within the context of tests.

Batch Apex Limitations

Keep in mind these governor limits and other limitations for batch Apex.
  • Up to 5 batch jobs can be queued or active concurrently.
  • Up to 100 Holding batch jobs can be held in the Apex flex queue.
  • In a running test, you can submit a maximum of 5 batch jobs.
  • The maximum number of batch Apex method executions per 24-hour period is 250,000, or the number of user licenses in your org multiplied by 200—whichever is greater. Method executions include executions of the start, execute, and finish methods. This limit is for your entire org and is shared with all asynchronous Apex: Batch Apex, Queueable Apex, scheduled Apex, and future methods. To check how many asynchronous Apex executions are available, make a request to REST API limits resource. See List Organization Limits in the REST API Developer Guide. If the number of asynchronous Apex executions needed by a job exceeds the available number that’s calculated using the 24-hour rolling limit, an exception is thrown. Batch Apex preemptively checks the required asynchronous job capacity when Database.executeBatch is called and the start method has returned the workload. The batch won’t start unless there is sufficient capacity for the entire job available. For example, if the batch requires 10,000 executions and the remaining asynchronous limit is 9,500 executions, an AsyncApexExecutions Limit exceeded exception is thrown, and the remaining executions are left unchanged. The license types that count toward this limit include full Salesforce and Salesforce Platform user licenses, App Subscription user licenses, Chatter Only users, Identity users, and Company Communities users.
  • A maximum of 50 million records can be returned in the Database.QueryLocator object. If more than 50 million records are returned, the batch job is immediately terminated and marked as Failed.
  • If the start method of the batch class returns a QueryLocator, the optional scope parameter of Database.executeBatch can have a maximum value of 2,000. If set to a higher value, Salesforce chunks the records returned by the QueryLocator into smaller batches of up to 2,000 records. If the start method of the batch class returns an iterable, the scope parameter value has no upper limit. However, if you use a high number, you can run into other limits. The optimal scope size is a factor of 2000, for example, 100, 200, 400 and so on.
  • If no size is specified with the optional scope parameter of Database.executeBatch, Salesforce chunks the records returned by the start method into batches of 200 records. The system then passes each batch to the execute method. Apex governor limits are reset for each execution of execute.
  • The start, execute, and finish methods can implement up to 100 callouts each.
  • Only one batch Apex job's start method can run at a time in an org. Batch jobs that haven’t started yet remain in the queue until they're started. This limit doesn’t cause any batch job to fail and execute methods of batch Apex jobs still run in parallel if more than one job is running.
  • Enqueued batch Apex jobs are processed when system resources become available. There’s no guarantee on how long it takes to start, execute, and finish the queued jobs. You can use the Apex flex queue to prioritize jobs.
  • Using FOR UPDATE in SOQL queries to lock records during update isn’t applicable to Batch Apex.
  • Database.QueryLocator objects and related query results are available for 2 days, including results in nested queries. For more information, see API Query Cursor Limits.

Batch Apex Considerations and Best Practices

  • Use extreme caution if you’re planning to invoke a batch job from a trigger. You must be able to guarantee that the trigger doesn’t add more batch jobs than the limit. In particular, consider API bulk updates, import wizards, mass record changes through the user interface, and all cases where more than one record can be updated at a time.
  • When you call Database.executeBatch, Salesforce only places the job in the queue. Actual execution can be delayed based on service availability and flex queue priority.
  • When testing your batch Apex, you can test only one execution of the execute method. Use the scope parameter of the executeBatch method to limit the number of records passed into the execute method to ensure that you aren’t running into governor limits.
  • The executeBatch method starts an asynchronous process. When you test batch Apex, make certain that the asynchronously processed batch job is finished before testing against the results. Use the Test methods startTest and stopTest around the executeBatch method to ensure that it finishes before continuing your test.
  • Use Database.Stateful with the class definition if you want to share instance member variables or data across job transactions. Otherwise, all member variables are reset to their initial state at the start of each transaction.
  • Methods declared as future aren’t allowed in classes that implement the Database.Batchable interface.
  • Methods declared as future can’t be called from a batch Apex class.
  • When a batch Apex job is run, email notifications are sent to the user who submitted the batch job. If the code is included in a managed package and the subscribing org is running the batch job, notifications are sent to the recipient listed in the Apex Exception Notification Recipient field.
  • Each method execution uses the standard governor limits anonymous block, Visualforce controller, or WSDL method.
  • Each batch Apex invocation creates an AsyncApexJob record. To construct a SOQL query to retrieve the job’s status, number of errors, progress, and submitter, use the AsyncApexJob record’s ID. For more information about the AsyncApexJob object, see AsyncApexJob in the Object Reference for Salesforce.
  • For each 10,000 AsyncApexJob records, Apex creates an AsyncApexJob record of type BatchApexWorker for internal use. When querying for all AsyncApexJob records, we recommend that you filter out records of type BatchApexWorker using the JobType field. Otherwise, the query returns one more record for every 10,000 AsyncApexJob records. For more information about the AsyncApexJob object, see AsyncApexJob in the Object Reference for Salesforce.
  • All implemented Database.Batchable interface methods must be defined as public or global.
  • For a sharing recalculation, we recommend that the execute method delete and then re-create all Apex managed sharing for the records in the batch. This process ensures that sharing is accurate and complete.
  • Batch jobs queued before a Salesforce service maintenance downtime remain in the queue. After service downtime ends and when system resources become available, the queued batch jobs are executed. If a batch job is running when downtime occurred, the batch execution is rolled back and restarted after the service comes back up. Because execute methods can therefore run multiple times, any non-transactional operations, such as callouts, can be retried. All non-transactional operations must follow Idempotent Design Considerations to maintain data integrity.
  • Minimize the number of batches, if possible. Salesforce uses a queue-based framework to handle asynchronous processes from such sources as future methods and batch Apex. This queue is used to balance request workload across organizations. If more than 2,000 unprocessed requests from a single organization are in the queue, any additional requests from the same organization are delayed while the queue handles requests from other organizations.
  • Salesforce recommends that you design your asynchronous Apex jobs to handle variations in processing time. For example, to handle potential processing overlaps, consider chaining batch jobs instead of scheduling jobs at fixed intervals.
  • Ensure that batch jobs execute as fast as possible. To ensure fast execution of batch jobs, minimize Web service callout times and tune the queries used in your batch Apex code. The longer the batch job executes, the more likely other queued jobs are delayed when many jobs are in the queue.
  • If you use batch Apex with Database.QueryLocator to access external objects via an OData adapter for Salesforce Connect:

    • Enable Request Row Counts on the external data source, and each response from the external system must include the total row count of the result set.
    • We recommend enabling Server-Driven Pagination on the external data source and having the external system determine page sizes and batch boundaries for large result sets. Typically, server-driven paging can adjust batch boundaries to accommodate changing datasets more effectively than client-driven paging.

      When Server-Driven Pagination is disabled on the external data source, the OData adapter controls the paging behavior (client-driven). If external object records are added to the external system while a job runs, other records can be processed twice. If external object records are deleted from the external system while a job runs, other records can be skipped.

    • When Server-Driven Pagination is enabled on the external data source, the batch size at runtime is the smaller of these two sizes:
      • Batch size specified in the scope parameter of Database.executeBatch. The default is 200 records.
      • Page size returned by the external system. We recommend that you set up your external system to return page sizes of 200 or fewer records.
  • Batch Apex jobs run faster when the start method returns a QueryLocator object that doesn't include related records via a subquery. Avoiding relationship subqueries in a QueryLocator allows batch jobs to run using a faster, chunked implementation. If the start method returns an iterable or a QueryLocator object with a relationship subquery, the batch job uses a slower, non-chunking, implementation. For example, if this query is used in the QueryLocator, the batch job uses a slower implementation because of the relationship subquery:
    1SELECT Id, (SELECT id FROM Contacts) FROM Account
    A better strategy is to perform the subquery separately, from within the execute method, which allows the batch job to run using the faster, chunking implementation.
  • To implement record locking as part of the batch job, you can requery records inside the execute method, using FOR UPDATE. Requerying records in this manner ensures that conflicting updates aren’t overwritten by DML in the batch job. To requery records, simply select the Id field in the batch job's main query locator.
  • The Salesforce Platform's flow control mechanism and fair-usage algorithm can cause a delay in running batch jobs.

Chaining Batch Jobs

Starting with API version 26.0, you can start another batch job from an existing batch job to chain jobs together. Chaining enforces strict sequential execution, ensuring that one job fully completes before the next one starts. This sequencing prevents situations where multiple batch jobs attempt to concurrently process the same records, which can lead to race conditions or data inconsistencies. Use chained batch jobs if you require sequential execution and batch processing, such as processing large data volumes. Otherwise, if batch processing isn’t needed, consider using Queueable Apex.

You can chain a batch job by calling Database.executeBatch or System.scheduleBatch from the finish method of the current batch class. The new batch job starts after the current batch job finishes.

A potential failure point in chained batch jobs is an unhandled exception within the job’s finish method. The unhandled exception prevents the next job from being enqueued and breaks the sequence. To safeguard against this point of failure, consider implementing a separate scheduled Apex job that periodically checks the status of the chain. The scheduled job queries the AsyncApexJob object for records where the JobType is 'BatchApex' and the ApexClass.Name matches the class expected to be currently running or queued within the chain. If this query returns no results, the expected job is neither running nor queued, which signifies that the chain has been unexpectedly interrupted. The scheduled job then restarts the entire batch chain, which prevents unprocessed records from accumulating and possibly reaching governor limits.

When creating a long chain of batch jobs, account for workload variations. If there's currently no further work to perform either in the current job’s finish method or because your business is entering an off-peak period, use System.scheduleBatch to add a delay before the execution of next chained batch job. This delay optimizes the usage of available batch jobs and the flex queue by preventing jobs that don't have any work from repeatedly starting.

For API version 25.0 and earlier, you can’t call Database.executeBatch or System.scheduleBatch from any batch Apex method.

The API version that’s used is the version of the running batch class that starts or schedules another batch job. If the finish method in the running batch class calls a method in a helper class to start the next batch job, the API version of the helper class doesn’t matter.

Note