⚡ Batchable Database.Batchable · stateful · flex queue

Batch Apex in Salesforce

When we work in Salesforce, Apex code runs with certain limits to protect system performance. These limits are usually not a problem when we handle a small number of records.

However, when a requirement involves processing thousands or millions of records at the same time, normal Apex code may fail due to limits such as CPU execution time, heap size, SOQL query limits, or DML limits.

Batch Apex is used in such situations. It allows Salesforce to process large amounts of data by splitting records into smaller groups and processing them one group at a time in the background, where each batch gets fresh governor limits.

What Is Batch Apex?

Batch Apex is a Salesforce mechanism used to process large volumes of records in a controlled and scalable way. Instead of processing all records in one transaction, Batch Apex breaks the work into smaller pieces and processes them one by one in the background. This approach prevents governor limit issues and ensures stable system performance.

Why We Need Batch Apex

In normal Apex execution:

Batch Apex solves this by:

What Is Database.Batchable?

Database.Batchable is a Salesforce-provided interface that tells the platform: "This class is designed to process data in batches." When a class implements Database.Batchable, Salesforce automatically splits records, controls transaction boundaries, and manages retries.

Why implement Database.Batchable<SObject>? We are telling Salesforce: this class works on records, Salesforce should call start/execute/finish, and handle batch execution automatically. Without it, the job cannot run as a batch.

Methods Required by Database.Batchable

The interface requires three methods: start(), execute(), finish() — Salesforce controls the execution order.

start() → execute()* → finish() batch lifecycle: query locator, scope transactions, finalize

How Salesforce Executes a Batch Job

  1. Calls start() once to collect records
  2. Splits records into batches (scopes)
  3. Calls execute() once per batch
  4. Creates a new transaction for each batch
  5. Resets governor limits for every batch
  6. Calls finish() once after all batches are processed

Understanding "Scope" in Batch Apex

A scope is a small group of records passed to execute(). Example: 10,000 records, batch size 200 → 50 execute calls. Each scope runs independently, own transaction, fresh limits.

What Makes Batch Apex Different from Normal Apex?

one transaction vs. many transactions normal = all or nothing ; batch = per 200 records

Common Use Cases

The Three Pillars of Batch Apex

Every batch class is built on three mandatory methods: start, execute, finish. Salesforce controls when they are called.

1. start() — record selection

Returns a Database.QueryLocator (most common, up to 50M records) or Iterable<SObject> for custom logic. Runs only once.

global Database.QueryLocator start(Database.BatchableContext bc) {
    return Database.getQueryLocator(
        'SELECT Id FROM Opportunity WHERE StageName = \'Closed Won\''
    );
}

2. execute() — core processing logic

Called once per batch, receives a scope list. DML, calculations, callouts (if enabled) happen here.

global void execute(Database.BatchableContext bc, List<Opportunity> scope) {
    for (Opportunity opp : scope) opp.Last_Processed_Date__c = Date.today();
    Database.update(scope, false);
}

3. finish() — post processing

Runs once after all batches: send emails, log summary, chain another batch.

global void finish(Database.BatchableContext bc) {
    System.debug('batch done');
}

Stateful Batch Apex (Database.Stateful)

By default, each execute gets a new instance — variables reset. Database.Stateful preserves instance variables across batches via serialization.

🔄 serialization between batches stateful: counters survive
global class StatefulBatch implements Database.Batchable<Account>, Database.Stateful {
    global Integer totalProcessed = 0;
    global void execute(... , List<Account> scope) { totalProcessed += scope.size(); }
    global void finish(...) { System.debug('total ' + totalProcessed); }
}

Preserved: instance variables (primitives, collections, wrappers). Not preserved: local vars, static vars, transaction state.

Batch Apex with Callouts

By default callouts are disabled. Implement Database.AllowsCallouts to enable HTTP in execute().

global class CalloutBatch implements Database.Batchable<Account>, Database.AllowsCallouts {
    global void execute(... , List<Account> scope) {
        Http http = new Http(); HttpRequest req = ...;
    }
}

Scheduling Batch Apex

Use Schedulable interface and System.schedule with CRON.

global class ClosedWonBatchScheduler implements Schedulable {
    global void execute(SchedulableContext sc) {
        Database.executeBatch(new UpdateClosedWonOppsBatch(), 200);
    }
}
// schedule: System.schedule('daily', '0 0 3 * * ?', new ClosedWonBatchScheduler());

Triggering Batch Apex

From LWC, Apex class, flow, or custom button via Database.executeBatch(new YourBatch(), batchSize);

Advanced Scenarios

Batch Chaining

Start another batch inside finish(): Database.executeBatch(new NextBatchJob(), 200);

Apex Flex Queue

Max 5 active + 100 queued jobs. Salesforce automatically manages overflow.

Error Handling

Use partial DML: Database.update(scope, false); and inspect SaveResult to log failures without stopping the batch.

📊 partial DML · error capturing resilient bulk processing

Testing Batch Apex

Batch is asynchronous, so tests need Test.startTest() and Test.stopTest() to force synchronous execution.

@IsTest static void testBatch() {
    // insert test data
    Test.startTest();
    Database.executeBatch(new UpdateClosedWonOppsBatch(), 200);
    Test.stopTest();
    // assertions after batch completes
}

Real‑World Example

Requirement: update Last_Processed_Date__c on all Closed Won Opportunities.

global class UpdateClosedWonOppsBatch implements Database.Batchable<Opportunity> {
    global Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator(
            'SELECT Id, Last_Processed_Date__c FROM Opportunity WHERE StageName = \'Closed Won\''
        );
    }
    global void execute(Database.BatchableContext bc, List<Opportunity> scope) {
        for (Opportunity opp : scope) opp.Last_Processed_Date__c = Date.today();
        Database.update(scope, false);
    }
    global void finish(Database.BatchableContext bc) {
        System.debug('Closed Won Opportunities processed.');
    }
}

Final summary

Batch Apex is Salesforce's solution for processing large data volumes safely. It splits work into smaller chunks, each with fresh limits.

Essential for enterprise background processing, data maintenance, and large‑scale automation.