Keeping State in Apex Batch Jobs: Why Database.Stateful Matters
Learn how Apex’s Database.Stateful lets batch jobs remember data between executions, the memory trade‑offs, and a step‑by‑step example that keeps a running total across 200,000+ records.
11 Sept 2026, 19:34 UTC

Problem: State Loss Between Batch Executions
When you run an Apex batch job, the platform splits the data set into smaller chunks (default 200 records per batch). Each chunk runs in a fresh transaction, so any instance variables you set in execute() are discarded after that chunk completes. If you need a cumulative result—like a running total, a list of processed record IDs, or a report that spans the entire data set—you lose that information unless you persist it elsewhere.
Thesis: Database.Stateful Lets You Preserve Instance Variables
Implementing the Database.Stateful interface tells Salesforce to keep the batch class instance alive across all chunk executions. This means you can safely accumulate data in instance fields and access the final state in finish(). It’s a lightweight alternative to writing to a database or external store for many use cases.
Why State Matters in Real Workflows
- Aggregating totals: e.g., total sales amount across 200,000 orders.
- Building a list of processed IDs for a follow‑up trigger or notification.
- Progressive data cleansing where each batch depends on the previous batch’s results.
Implementing a Stateful Batch Class
Below is a minimal example that collects the Ids of all records processed. Replace the query and logic with your own business rules.
global class CollectIdsBatch implements Database.Batchable<SObject>, Database.Stateful {
// Accumulated state – a list of processed Ids
global List<Id> processedIds = new List<Id>();
global Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator(
[SELECT Id FROM MyObject__c WHERE Status__c = 'Pending']
);
}
global void execute(Database.BatchableContext bc, List<SObject> scope) {
// Add the Ids from this chunk to the running list
for (SObject s : scope) {
processedIds.add(((MyObject__c)s).Id);
}
}
global void finish(Database.BatchableContext bc) {
// At this point, processedIds contains all Ids from the job
System.debug('Total processed records: ' + processedIds.size());
// Example: send an email with the count
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
mail.setToAddresses(new String[] {'[contact removed]'});
mail.setSubject('Batch Job Complete');
mail.setPlainTextBody('Processed ' + processedIds.size() + ' records.');
Messaging.sendEmail(new Messaging.SingleEmailMessage[] {mail});
}
}
To enqueue this batch: Database.executeBatch(new CollectIdsBatch(), 200); (200 is the default scope size).
Memory & Failure Trade‑offs
| Aspect | Benefit | Risk |
|---|---|---|
| Heap Usage | Only the instance fields are kept in memory. | Each batch still has a 6 MB heap limit; large collections can exceed it. |
| Failure Handling | All accumulated state is lost if the job fails. | Persist critical data to a database or external store if you need recovery. |
| Recursion Risk | None in isolation. | Using state inside trigger‑based batches can cause unintended side‑effects if not cleared properly. |
Practical Verification Steps
- Deploy the batch class to a sandbox.
- Run it with a large data set (e.g., 200,000+ records).
- Check the debug log for
Heap Sizeper transaction; it should stay below 6 MB. - In the
finish()debug line, verify theprocessedIds.size()matches the expected count. - Run a unit test that enqueues the batch and asserts the final count.
Actionable Checklist for Developers
- Implement
Database.Statefulonly when you need to keep simple data (e.g., counters, lists of IDs). - Keep instance fields small; avoid storing query results or large collections.
- Monitor heap usage in debug logs during development.
- If the job may fail, persist the state to a custom object or external system before the job completes.
- Clear or reset instance fields at the beginning of
execute()if you run the same batch class instance multiple times in a single org session.
Conclusion
Database.Stateful gives Apex batch jobs a lightweight way to maintain state across thousands of records without the overhead of external persistence. By understanding the heap limits and failure semantics, you can design robust, efficient batch processes that deliver accurate cumulative results.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.