Batch Apex 101: Splitting Data, Avoiding Governor Limits, and Practical Usage
Learn how Salesforce Batch Apex splits large datasets into chunks, the best‑practice workflow, a hands‑on example, and key trade‑offs that can save you from hitting governor limits.
19 Jul 2026, 21:26 UTC

The Problem – Large Data, Governor Limits
When you need to touch thousands or millions of records in Salesforce, a single Apex transaction is bound by strict governor limits. CPU time, SOQL queries, and DML rows are all capped, so a bulk update that would normally finish in a few seconds in a local environment can exceed the limits and fail in the cloud.
What Governor Limits Happen
- Maximum 10 000 SOQL queries per transaction
- Maximum 10 000 DML rows per transaction
- Maximum 10 000 DML statements per transaction
- Maximum 10 000 CPU milliseconds per transaction (approximately 10 minutes)
These limits are enforced per execution context. If you try to process a large set in a single context, you will hit one of these limits and the transaction will roll back.
How Batch Apex Solves It
Batch Apex splits a large dataset into smaller “chunks” and processes each chunk in its own transaction. By doing so, each chunk stays within the governor limits even if the overall dataset is huge.
The Three Methods – start, execute, finish
start: Returns aDatabase.QueryLocatoror anIterablethat defines the set of records to process.execute: Runs once per chunk. Receives a list of records and can perform any logic, including SOQL, DML, and calls to external services.finish: Runs after all chunks have completed. Useful for sending notifications or chaining another batch.
Chunk Size and the Apex Flex Queue
You can pass the desired chunk size to Database.executeBatch. A common value is 200, which is the default. Smaller chunks reduce the risk of exceeding the 10‑minute hard limit per execute call but increase the number of transactions, which can affect overall runtime.
Batch jobs are queued in the Apex Flex Queue. Up to five jobs run concurrently; any additional jobs wait until a slot becomes available. This behavior influences how you schedule heavy jobs—spread them out to avoid contention and to keep the queue from filling up.
A Concrete Example
Below is a minimal batch class that updates a custom field Last_Processed__c on Account records. Replace the field name and logic with your own use case.
global class UpdateAccountBatch implements Database.Batchable<Account> {
global Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator(
[SELECT Id, Name, Last_Processed__c FROM Account WHERE Last_Processed__c = NULL]
);
}
global void execute(Database.BatchableContext bc, List<Account> scope) {
for (Account a : scope) {
a.Last_Processed__c = System.now();
}
update scope;
}
global void finish(Database.BatchableContext bc) {
// Optional: send a notification or trigger another job
}
}
To run the batch immediately with a chunk size of 200 records:
Database.executeBatch(new UpdateAccountBatch(), 200);
Run the above in the Developer Console or an anonymous Apex window. You need Author Apex and Modify All Data permissions to execute the batch.
Trade‑offs and Limitations
- Each
executecall runs in its own transaction. Trigger logic, workflow rules, and process builder flows fire separately for each chunk, which can lead to duplicate side‑effects if not accounted for. - Because the 10‑minute limit applies per chunk, you must keep the chunk size small enough that the
executemethod finishes in under 10 minutes. Complex logic or external calls can quickly exhaust this time. - Batch jobs are subject to the same 5‑job concurrency limit. If you schedule many batches at once, some will queue and start later than expected.
- Debugging can be more involved because each chunk has its own execution context. Use the Apex Jobs page to correlate logs to specific chunks.
How to Verify It Works
- After running
Database.executeBatch, navigate to Setup > Jobs > Apex Jobs.- Confirm the job appears as Queued and later transitions to Processing and Completed.
- Click the job name to view the debug logs for each
executechunk.
- Run a SOQL query to verify that the
Last_Processed__cfield has been populated on the expected records.- Example:
SELECT Id, Last_Processed__c FROM Account WHERE Last_Processed__c != NULL LIMIT 10
- Example:
- If you scheduled the batch with
System.schedule, check the job history after the scheduled time to ensure it ran automatically.
Takeaway & Next Steps
Batch Apex is the go‑to pattern for processing large volumes of data while staying within governor limits. By carefully choosing chunk size, understanding the Flex Queue, and accounting for trigger side‑effects, you can build reliable, scalable data operations.
Next steps:
- Experiment with different chunk sizes and observe the impact on runtime and queue state.
- Implement
finishlogic to chain a follow‑up batch or send a notification email. - Use the
Database.setBatchSizemethod if you need to adjust the default chunk size globally for a particular batch run.
Remember to monitor the Apex Jobs page and debug logs regularly during development to catch any unexpected trigger or workflow behavior.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.