When dealing with Apex Batch jobs in Salesforce, maintaining state across batch executions is often necessary, especially when tracking processed records, aggregating results, or handling complex logic. The Database.Stateful interface in Apex allows us to persist variables between batch executions, overcoming the typical stateless nature of batch jobs.
In this blog post, we’ll explore how Database.Stateful works, when to use it, and provide real-world examples.
Understanding Database.Stateful
By default, Apex batch jobs do not retain class variables between batch executions. Each batch chunk runs in a separate transaction, meaning instance variables reset after each execution. Implementing Database.Stateful ensures that instance variables persist across all batch executions.
Syntax:
public class MyStatefulBatch implements Database.Batchable<SObject>, Database.Stateful {
private Integer totalProcessed = 0;
public Database.QueryLocator start(Database.BatchableContext BC) {
return Database.getQueryLocator('SELECT Id FROM Account');
}
public void execute(Database.BatchableContext BC, List<Account> scope) {
totalProcessed += scope.size(); // Retains the count across batch executions
}
public void finish(Database.BatchableContext BC) {
System.debug('Total records processed: ' + totalProcessed);
}
}
Here, totalProcessed retains its value across all batch executions instead of resetting for each batch chunk.
When to Use Database.Stateful
- Tracking processed records: When you need to count or log processed records across batch executions.
- Aggregating data: Summing up values, tracking totals, or accumulating information across records.
- Managing shared state: Keeping track of processed IDs, collecting logs, or storing temporary results.
Important Consideration: Using Database.Stateful increases heap size usage, so use it only when necessary to avoid hitting governor limits.
Real-World Examples
Example 1: Counting Processed Records
This batch job updates Contacts and keeps a count of how many were processed:
public class ContactBatchStateful implements Database.Batchable<SObject>, Database.Stateful {
private Integer contactCount = 0;
public Database.QueryLocator start(Database.BatchableContext BC) {
return Database.getQueryLocator('SELECT Id, LastName FROM Contact');
}
public void execute(Database.BatchableContext BC, List<Contact> scope) {
for (Contact c : scope) {
c.LastName = 'Updated';
}
update scope;
contactCount += scope.size();
}
public void finish(Database.BatchableContext BC) {
System.debug('Total contacts processed: ' + contactCount);
}
}
Execution:
Database.executeBatch(new ContactBatchStateful(), 200);
In this example, contactCount persists across batch chunks, ensuring we get the total count at the end.
Example 2: Summing Up Opportunity Amounts
A company wants to calculate the total Amount of all closed Opportunities using a batch job.
public class OpportunityAmountBatch implements Database.Batchable<SObject>, Database.Stateful {
private Decimal totalRevenue = 0;
public Database.QueryLocator start(Database.BatchableContext BC) {
return Database.getQueryLocator('SELECT Amount FROM Opportunity WHERE StageName = 'Closed Won'');
}
public void execute(Database.BatchableContext BC, List<Opportunity> scope) {
for (Opportunity opp : scope) {
totalRevenue += opp.Amount;
}
}
public void finish(Database.BatchableContext BC) {
System.debug('Total revenue from Closed Won opportunities: ' + totalRevenue);
}
}
This batch job ensures that totalRevenue retains its value across different batch executions.
Example 3: Storing Processed Record IDs
If you need to store a list of processed record IDs, Database.Stateful can be helpful:
public class ProcessedAccountsBatch implements Database.Batchable<SObject>, Database.Stateful {
private Set<Id> processedIds = new Set<Id>();
public Database.QueryLocator start(Database.BatchableContext BC) {
return Database.getQueryLocator('SELECT Id FROM Account WHERE Industry = 'Technology'');
}
public void execute(Database.BatchableContext BC, List<Account> scope) {
for (Account acc : scope) {
processedIds.add(acc.Id);
}
}
public void finish(Database.BatchableContext BC) {
System.debug('Processed Account IDs: ' + processedIds);
}
}
This batch retains processed IDs across chunks, allowing us to track which records were handled.
Best Practices
- Avoid excessive heap size usage: Retaining large collections of data (e.g., storing thousands of record IDs) can cause heap limit errors.
- Use only when needed: If batch processing does not require state retention, avoid
Database.Statefulto optimize performance. - Monitor governor limits: Keep an eye on heap size and optimize queries if needed.
Conclusion
Using Database.Stateful in Apex Batch classes enables tracking, aggregating, and persisting data across batch executions. While it is a powerful feature, it should be used judiciously to avoid excessive memory consumption. By understanding when and how to use it, developers can create more efficient and robust batch processing solutions in Salesforce.

We would love to hear your comments!