Skip to main content

SF-0380 · Scenario · Medium

What will happen if we don't use Database.Stateful?

✓ Verified by Vikas Singhal · Last reviewed 5/17/2026 · Updated for Spring '26

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

ThingPersists across chunks?
Instance fields (initialized at construction)No — reset to constructor default every chunk
Static variablesNo — 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 positionYes — 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 → call execute.
  • With Database.Stateful: deserialize the full prior state → call execute.

Without the interface, the platform doesn’t bother restoring fields — it just gives you a clean slate.

Why anyone would skip Database.Stateful

ScenarioSkip Stateful?
Each record processed independently — no aggregationYes, skip it
You just want to update records, no counters or rollups neededYes, skip it
You need totals, error lists, or cross-chunk mapsNo, add it
You want a clean instance per chunk for memory hygieneYes, 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 transient matter? — Yes, transient fields 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.