Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
Full Batch Apex entry
How-to guide

How to write a Batch Apex job

Batch Apex is the standard way to process large data volumes on Salesforce. The structure is simple (three methods, one interface) but the design choices matter: scope size, state preservation, error handling, and chaining all need deliberate decisions. Build and tune in a sandbox with realistic data volumes before running in production.

By Dipojjal Chakrabarti · Founder & Editor, Salesforce DictionaryLast updated May 16, 2026

Batch Apex is the standard way to process large data volumes on Salesforce. The structure is simple (three methods, one interface) but the design choices matter: scope size, state preservation, error handling, and chaining all need deliberate decisions. Build and tune in a sandbox with realistic data volumes before running in production.

  1. Confirm Batch Apex is the right tool

    Use Batch Apex when you need to process more records than a synchronous transaction can handle. For smaller async work with complex data, Queueable Apex is simpler. For cron-based scheduling, use Schedulable Apex that delegates to a batch job for the actual work.

  2. Create the class with Database.Batchable interface

    public with sharing class MyBatchJob implements Database.Batchable<sObject> { ... }. Implement start, execute, and finish methods. Use sObject as the generic type for most jobs; use a custom type for non-SObject iterables.

  3. Implement start to return the records to process

    Return a Database.QueryLocator for SOQL-based selection: return Database.getQueryLocator(''SELECT Id, Name FROM Account WHERE Stale__c = true''). QueryLocator supports up to 50 million records. For custom data sources, return an Iterable<sObject> instead, which has lower limits.

  4. Implement execute with per-chunk business logic

    Receive List<sObject> records as the second parameter. Process them with normal Apex logic: SOQL queries, DML operations, callouts (if Database.AllowsCallouts is also implemented). Each chunk has its own governor limits, so bulkify carefully within the chunk.

  5. Implement finish for post-job cleanup

    Send completion notifications, log job summary to a custom Error_Log__c, or chain the next batch job via Database.executeBatch. Use the Database.BatchableContext parameter to get the job ID for queries against AsyncApexJob.

  6. Add Database.Stateful if aggregation is needed

    Implements Database.Batchable<sObject>, Database.Stateful. Instance variables preserved across chunks. Use for running totals, accumulated error lists, or any state that must survive from execute to finish.

  7. Write a test class with realistic data volumes

    @isTest class with test methods that build 200+ records and call Database.executeBatch from inside Test.startTest/Test.stopTest. Confirm execute fires the right number of times and finish runs once. Test failures, retries, and edge cases.

  8. Schedule or invoke and monitor the job

    Run from Anonymous Apex: Database.executeBatch(new MyBatchJob(), 200). Schedule via Schedulable Apex for recurring execution. Monitor Setup > Apex Jobs for status. Build a dashboard against AsyncApexJob for production visibility.

Key options
Scope Sizeremember

Records per execute invocation. Default 200, up to 2,000 for QueryLocator jobs. Tune based on per-record work and governor limit headroom.

Database.Statefulremember

Optional interface for preserving instance state across chunks. Required for cross-chunk aggregation and accumulated results.

Database.AllowsCalloutsremember

Optional interface that lets execute methods make HTTP callouts to external systems. Required for jobs that need external integration.

Gotchas
  • QueryLocator supports up to 50 million records, but Iterable scope is much smaller. If the data source is not directly queryable via SOQL, profile the iterable performance before assuming it scales.
  • Each chunk runs in its own transaction. Instance variables on the batch class do not preserve across chunks unless Database.Stateful is implemented. Forgetting this is the leading cause of mysterious zero-totals at finish.
  • Async governor limits apply across the whole job (cumulative callouts, total CPU, future method calls). Long-running jobs can still hit these even though per-chunk limits reset.
  • Failed chunks roll back their DML but do not stop the job. The job continues processing remaining chunks. Build error tracking into execute (try/catch with Error_Log__c) because the default behavior silently swallows partial failures.
  • Chained batch jobs from finish can produce infinite loops if termination criteria are wrong. Always include explicit stop conditions and monitor AsyncApexJob for runaway chains in production.

See the full Batch Apex entry

Batch Apex includes the definition, worked example, deep dive, related terms, and a quiz.