Batch Apex in Salesforce
When we work in Salesforce, Apex code runs with certain limits to protect system performance. These limits are usually not a problem when we handle a small number of records.
However, when a requirement involves processing thousands or millions of records at the same time, normal Apex code may fail due to limits such as CPU execution time, heap size, SOQL query limits, or DML limits.
Batch Apex is used in such situations. It allows Salesforce to process large amounts of data by splitting records into smaller groups and processing them one group at a time in the background, where each batch gets fresh governor limits.
What Is Batch Apex?
Batch Apex is a Salesforce mechanism used to process large volumes of records in a controlled and scalable way. Instead of processing all records in one transaction, Batch Apex breaks the work into smaller pieces and processes them one by one in the background. This approach prevents governor limit issues and ensures stable system performance.
Why We Need Batch Apex
In normal Apex execution:
- All records are processed in one transaction
- Governor limits apply once for the entire operation
- Large data volumes often cause failures
Batch Apex solves this by:
- Dividing records into batches
- Processing each batch in a separate transaction
- Resetting limits for every batch
What Is Database.Batchable?
Database.Batchable is a Salesforce-provided interface that tells the platform: "This class is designed to process data in batches." When a class implements Database.Batchable, Salesforce automatically splits records, controls transaction boundaries, and manages retries.
Why implement Database.Batchable<SObject>? We are telling Salesforce: this class works on records, Salesforce should call start/execute/finish, and handle batch execution automatically. Without it, the job cannot run as a batch.
Methods Required by Database.Batchable
The interface requires three methods:
start(), execute(), finish() —
Salesforce controls the execution order.
How Salesforce Executes a Batch Job
- Calls start() once to collect records
- Splits records into batches (scopes)
- Calls execute() once per batch
- Creates a new transaction for each batch
- Resets governor limits for every batch
- Calls finish() once after all batches are processed
Understanding "Scope" in Batch Apex
A scope is a small group of records passed to execute(). Example: 10,000 records, batch size 200 → 50 execute calls. Each scope runs independently, own transaction, fresh limits.
What Makes Batch Apex Different from Normal Apex?
Common Use Cases
- Mass record updates
- Archiving old data
- Historical recalculations
- Scheduled nightly jobs
- Integration reconciliation
The Three Pillars of Batch Apex
Every batch class is built on three mandatory methods: start, execute, finish. Salesforce controls when they are called.
1. start() — record selection
Returns a Database.QueryLocator (most common, up to 50M records) or Iterable<SObject> for custom logic. Runs only once.
global Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator(
'SELECT Id FROM Opportunity WHERE StageName = \'Closed Won\''
);
}
2. execute() — core processing logic
Called once per batch, receives a scope list. DML, calculations, callouts (if enabled) happen here.
global void execute(Database.BatchableContext bc, List<Opportunity> scope) {
for (Opportunity opp : scope) opp.Last_Processed_Date__c = Date.today();
Database.update(scope, false);
}
3. finish() — post processing
Runs once after all batches: send emails, log summary, chain another batch.
global void finish(Database.BatchableContext bc) {
System.debug('batch done');
}
Stateful Batch Apex (Database.Stateful)
By default, each execute gets a new instance — variables reset. Database.Stateful preserves instance variables across batches via serialization.
global class StatefulBatch implements Database.Batchable<Account>, Database.Stateful {
global Integer totalProcessed = 0;
global void execute(... , List<Account> scope) { totalProcessed += scope.size(); }
global void finish(...) { System.debug('total ' + totalProcessed); }
}
Preserved: instance variables (primitives, collections, wrappers). Not preserved: local vars, static vars, transaction state.
Batch Apex with Callouts
By default callouts are disabled. Implement Database.AllowsCallouts to enable HTTP in execute().
global class CalloutBatch implements Database.Batchable<Account>, Database.AllowsCallouts {
global void execute(... , List<Account> scope) {
Http http = new Http(); HttpRequest req = ...;
}
}
Scheduling Batch Apex
Use Schedulable interface and
System.schedule with CRON.
global class ClosedWonBatchScheduler implements Schedulable {
global void execute(SchedulableContext sc) {
Database.executeBatch(new UpdateClosedWonOppsBatch(), 200);
}
}
// schedule: System.schedule('daily', '0 0 3 * * ?', new ClosedWonBatchScheduler());
Triggering Batch Apex
From LWC, Apex class, flow, or custom button via
Database.executeBatch(new YourBatch(), batchSize);
Advanced Scenarios
Batch Chaining
Start another batch inside finish():
Database.executeBatch(new NextBatchJob(), 200);
Apex Flex Queue
Max 5 active + 100 queued jobs. Salesforce automatically manages overflow.
Error Handling
Use partial DML: Database.update(scope, false); and
inspect SaveResult to log failures without stopping the batch.
Testing Batch Apex
Batch is asynchronous, so tests need Test.startTest() and
Test.stopTest() to force synchronous execution.
@IsTest static void testBatch() {
// insert test data
Test.startTest();
Database.executeBatch(new UpdateClosedWonOppsBatch(), 200);
Test.stopTest();
// assertions after batch completes
}
Real‑World Example
Requirement: update
Last_Processed_Date__c on all Closed Won Opportunities.
global class UpdateClosedWonOppsBatch implements Database.Batchable<Opportunity> {
global Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator(
'SELECT Id, Last_Processed_Date__c FROM Opportunity WHERE StageName = \'Closed Won\''
);
}
global void execute(Database.BatchableContext bc, List<Opportunity> scope) {
for (Opportunity opp : scope) opp.Last_Processed_Date__c = Date.today();
Database.update(scope, false);
}
global void finish(Database.BatchableContext bc) {
System.debug('Closed Won Opportunities processed.');
}
}
Final summary
Batch Apex is Salesforce's solution for processing large data volumes safely. It splits work into smaller chunks, each with fresh limits.
- Each batch runs in a separate transaction
- Database.Stateful preserves instance variables across batches
- Supports callouts, scheduling, chaining, and error handling
- Proper testing uses
Test.startTest/stopTest
Essential for enterprise background processing, data maintenance, and large‑scale automation.