Skip to main content

SF-0367 · Coding · Medium

Can you write a sample Batch apex class?

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

Here’s a complete, production-shaped Batch Apex class — the kind interviewers want to see (not the textbook stub).

The class

/**
 * Auto-closes Cases that have been New for more than 30 days.
 * Tracks totals across chunks (Database.Stateful) and emails a
 * summary at the end.
 */
public class StaleCaseCloserBatch implements
    Database.Batchable<sObject>,
    Database.Stateful
{
    private Integer totalProcessed = 0;
    private Integer totalErrors = 0;
    private List<String> errorMessages = new List<String>();

    public Database.QueryLocator start(Database.BatchableContext ctx) {
        return Database.getQueryLocator(
            'SELECT Id, Subject, Status, AccountId, OwnerId ' +
            'FROM Case ' +
            'WHERE Status = \'New\' ' +
            '  AND CreatedDate < LAST_N_DAYS:30'
        );
    }

    public void execute(Database.BatchableContext ctx, List<Case> scope) {
        for (Case c : scope) {
            c.Status = 'Auto-Closed';
            c.Description = (c.Description != null ? c.Description + '\n' : '') +
                            '[' + Datetime.now() + '] Auto-closed by stale-case batch.';
        }
        Database.SaveResult[] results = Database.update(scope, false);

        for (Integer i = 0; i < results.size(); i++) {
            if (results[i].isSuccess()) {
                totalProcessed++;
            } else {
                totalErrors++;
                errorMessages.add(
                    'Case ' + scope[i].Id + ': ' +
                    results[i].getErrors()[0].getMessage()
                );
            }
        }
    }

    public void finish(Database.BatchableContext ctx) {
        AsyncApexJob job = [
            SELECT Status, JobItemsProcessed, TotalJobItems, CreatedBy.Email
            FROM AsyncApexJob WHERE Id = :ctx.getJobId()
        ];

        Messaging.SingleEmailMessage msg = new Messaging.SingleEmailMessage();
        msg.setToAddresses(new String[] { job.CreatedBy.Email });
        msg.setSubject('Stale Case Closer: ' + job.Status);
        msg.setPlainTextBody(
            'Chunks: ' + job.JobItemsProcessed + ' of ' + job.TotalJobItems + '\n' +
            'Records updated: ' + totalProcessed + '\n' +
            'Errors: ' + totalErrors + '\n\n' +
            (totalErrors > 0
                ? 'First 10 errors:\n' + String.join(safeFirst(errorMessages, 10), '\n')
                : 'No errors.')
        );
        Messaging.sendEmail(new Messaging.SingleEmailMessage[] { msg });
    }

    private List<String> safeFirst(List<String> list, Integer n) {
        if (list.size() <= n) return list;
        List<String> top = new List<String>();
        for (Integer i = 0; i < n; i++) top.add(list[i]);
        return top;
    }
}

How to invoke

Id jobId = Database.executeBatch(new StaleCaseCloserBatch(), 200);

What this example demonstrates

FeatureShown by
Database.Batchable<sObject>Class declaration
Database.StatefulMember variables persist across execute
Database.QueryLocatorUsed in start for up to 50M records
Partial-success DMLDatabase.update(scope, false) — failed records don’t fail the chunk
Per-record error trackingIterating SaveResult[]
AsyncApexJob query in finishReports job status
Notification via emailStandard pattern

A simpler version

If they just want the bare minimum:

public class SimpleBatch implements Database.Batchable<sObject> {
    public Database.QueryLocator start(Database.BatchableContext ctx) {
        return Database.getQueryLocator('SELECT Id FROM Account WHERE Active__c = false');
    }
    public void execute(Database.BatchableContext ctx, List<Account> scope) {
        for (Account a : scope) a.Active__c = true;
        update scope;
    }
    public void finish(Database.BatchableContext ctx) {}
}

The version with callouts

If execute makes HTTP callouts, add Database.AllowsCallouts:

public class SyncBatch implements
    Database.Batchable<sObject>,
    Database.AllowsCallouts
{
    public Database.QueryLocator start(Database.BatchableContext ctx) { /* ... */ }
    public void execute(Database.BatchableContext ctx, List<Account> scope) {
        for (Account a : scope) {
            HttpRequest req = new HttpRequest();
            req.setEndpoint('callout:ERP/accounts/' + a.Id);
            req.setMethod('PUT');
            new Http().send(req);
        }
    }
    public void finish(Database.BatchableContext ctx) {}
}

Verified against: Apex Developer Guide — Using Batch Apex. Last reviewed 2026-05-17.