Using Batch Apex is a three-step process:
- Write a class that implements
Database.Batchable<sObject>. - Implement the three required methods —
start,execute,finish. - Invoke it with
Database.executeBatch(new YourBatch(), batchSize).
Step 1: Write the class
public class InactiveCaseCleanup implements Database.Batchable<sObject> {
public Database.QueryLocator start(Database.BatchableContext ctx) {
return Database.getQueryLocator(
'SELECT Id, Status FROM Case WHERE Status = \'New\' ' +
'AND CreatedDate < LAST_N_DAYS:90'
);
}
public void execute(Database.BatchableContext ctx, List<Case> scope) {
for (Case c : scope) {
c.Status = 'Auto-Closed';
c.Description = 'Closed by 90-day cleanup batch ' + Date.today();
}
update scope;
}
public void finish(Database.BatchableContext ctx) {
// Optional: send completion email, chain another batch
}
}
Step 2: Understand each method’s role
| Method | Runs | Purpose |
|---|---|---|
start | Once | Define the dataset (QueryLocator or Iterable) |
execute | Once per chunk (default 200 records) | Process this chunk |
finish | Once | Notify / chain / cleanup |
Step 3: Invoke it
From the Developer Console, anonymous Apex, or any class:
// Default chunk size of 200 records
Id jobId = Database.executeBatch(new InactiveCaseCleanup());
// Custom chunk size (1 to 2000)
Id jobId = Database.executeBatch(new InactiveCaseCleanup(), 100);
Database.executeBatch returns the JobId synchronously. The actual batch runs async — you can monitor it via Setup → Apex Jobs or query AsyncApexJob.
Choose the right chunk size
| Workload | Suggested chunk size |
|---|---|
| Simple field updates, no related-record DML | 200 (default) |
| Triggers/flows on related records cascade | 50–100 |
| Heavy callouts (one per record) | 10–25 |
| Mass deletes of cold records | 200–500 |
Smaller chunks = less risk of hitting CPU/SOQL limits per chunk, but more chunks total. Larger chunks = fewer transactions, but higher chance of governor blowouts.
When to add Database.Stateful
If you need to track totals or accumulated errors across chunks, add Database.Stateful:
public class InactiveCaseCleanup implements Database.Batchable<sObject>, Database.Stateful {
private Integer totalProcessed = 0;
// ... totalProcessed survives across executes
}
When to add Database.AllowsCallouts
If execute makes HTTP callouts:
public class SyncBatch implements Database.Batchable<sObject>, Database.AllowsCallouts {
public void execute(Database.BatchableContext ctx, List<Account> scope) {
Http http = new Http();
// ... callouts allowed here
}
}
Common pitfalls
- No bulkified DML in
execute— you’ll blow SOQL/DML limits fast. Process the entirescopein one update, not per-record. - No
@futurefromexecute— throwsAsyncException. Use Queueable instead (fromfinish). - Mixing data setup in
start—startis for the query, not for side effects. Side effects belong inexecute/finish.
Common interview follow-ups
- What’s the smallest chunk size? — 1.
- What’s the largest? — 2,000.
- How many batches can run concurrently? — 5 active + 100 holding in the Apex Flex Queue.
- Can I chain another batch? — Yes — call
Database.executeBatchfromfinish.
Verified against: Apex Developer Guide — Using Batch Apex. Last reviewed 2026-05-17.