Without Database.Stateful, every execute invocation receives a freshly constructed instance of your batch class. Any value you stored in an instance field during the previous chunk is gone. The same is true for finish — it sees a clean instance, not the state built up across chunks.
Demonstration
// NO Database.Stateful
public class StatelessBatch implements Database.Batchable<sObject> {
public Integer totalProcessed = 0;
public List<String> errors = new List<String>();
public Database.QueryLocator start(Database.BatchableContext ctx) {
return Database.getQueryLocator('SELECT Id FROM Account LIMIT 600');
}
public void execute(Database.BatchableContext ctx, List<Account> scope) {
totalProcessed += scope.size();
System.debug('Chunk processed; running total: ' + totalProcessed);
}
public void finish(Database.BatchableContext ctx) {
System.debug('Final total: ' + totalProcessed); // shows 0
}
}
With 600 records and default chunk size 200, you get 3 chunks. Debug output:
Chunk processed; running total: 200
Chunk processed; running total: 200
Chunk processed; running total: 200
Final total: 0
Each chunk starts with totalProcessed = 0, increments to 200, then dies. finish runs on yet another fresh instance, so it sees the default 0.
What actually persists without Database.Stateful
| Thing | Persists across chunks? |
|---|---|
| Instance fields (initialized at construction) | No — reset to constructor default every chunk |
| Static variables | No — statics live for one transaction, each chunk is a new transaction |
| Database records (after DML commit) | Yes — that’s the database, not in-memory state |
| The QueryLocator’s cursor position | Yes — platform-managed |
Nothing in your class’s memory survives. Only data persisted to the database does.
What “fresh instance” means
When you call Database.executeBatch(new MyBatch()), you create one instance. The platform serializes that instance, stores it, and for every chunk:
- Without
Database.Stateful: deserialize → call constructor with no args / use defaults → assign no state → callexecute. - With
Database.Stateful: deserialize the full prior state → callexecute.
Without the interface, the platform doesn’t bother restoring fields — it just gives you a clean slate.
Why anyone would skip Database.Stateful
| Scenario | Skip Stateful? |
|---|---|
| Each record processed independently — no aggregation | Yes, skip it |
| You just want to update records, no counters or rollups needed | Yes, skip it |
| You need totals, error lists, or cross-chunk maps | No, add it |
| You want a clean instance per chunk for memory hygiene | Yes, skip it |
Adding Database.Stateful has a small overhead — Salesforce serializes/deserializes the instance between chunks. If you don’t need state, don’t pay that cost.
Constructor params still survive — kind of
Things you pass to the constructor and store in instance fields behave differently:
public class FilterBatch implements Database.Batchable<sObject> {
private String statusFilter; // set in constructor — survives chunks
public FilterBatch(String s) { this.statusFilter = s; }
public Database.QueryLocator start(Database.BatchableContext ctx) {
return Database.getQueryLocator(
'SELECT Id FROM Case WHERE Status = :statusFilter'
);
}
public void execute(Database.BatchableContext ctx, List<Case> scope) {
// statusFilter is still set here, even without Database.Stateful
}
public void finish(Database.BatchableContext ctx) {}
}
Constructor-initialized fields are rehydrated from the serialized class instance each chunk — they essentially behave the same as Database.Stateful for read-only purposes. The difference is mutated state inside execute doesn’t carry forward without the interface.
Common interview follow-ups
- Why is constructor state restored but mutated state isn’t? — The serialized instance kept by the platform reflects the post-constructor state. Without
Stateful, that snapshot is the only state you ever get — mutations don’t get re-serialized between chunks. - Does
transientmatter? — Yes,transientfields are not serialized regardless. They reset every chunk. - Can I work around this with statics? — No, statics also reset.
Verified against: Apex Developer Guide — Using Batch Apex. Last reviewed 2026-05-17.