Skip to main content

SF-0382 · Scenario · Medium

Can we callouts in batch apex?

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

Yes — but you must opt in by implementing Database.AllowsCallouts. Without that marker interface, any HTTP callout from execute throws:

System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out.

The pattern

public class SyncBatch implements
    Database.Batchable<sObject>,
    Database.AllowsCallouts
{
    public Database.QueryLocator start(Database.BatchableContext ctx) {
        return Database.getQueryLocator(
            'SELECT Id, Name FROM Account WHERE Sync_Pending__c = true'
        );
    }

    public void execute(Database.BatchableContext ctx, List<Account> scope) {
        Http http = new Http();
        for (Account a : scope) {
            HttpRequest req = new HttpRequest();
            req.setEndpoint('callout:ERP_Credentials/accounts/' + a.Id);
            req.setMethod('PUT');
            req.setHeader('Content-Type', 'application/json');
            req.setBody(JSON.serialize(a));
            req.setTimeout(60000);

            try {
                HttpResponse resp = http.send(req);
                a.Sync_Pending__c = (resp.getStatusCode() >= 300);
                a.Last_Sync_Status__c = String.valueOf(resp.getStatusCode());
            } catch (CalloutException e) {
                a.Last_Sync_Status__c = 'ERROR: ' + e.getMessage();
            }
        }
        update scope;
    }

    public void finish(Database.BatchableContext ctx) {}
}

Why the marker interface is needed

Batch Apex execute runs in an async transaction after DML on records in start’s query has been committed — so the “uncommitted work pending” rule shouldn’t apply. But the platform still requires you to declare intent via Database.AllowsCallouts. It’s a deliberate opt-in to remind you about the callout-related limits.

Limits to know

Limit (per chunk transaction)Value
Callouts100
Callout timeout (per request)120 seconds
Total callout timeSubject to CPU cap (60 seconds async)

For batches that callout per record:

chunk_size × callouts_per_record ≤ 100

If each record needs 1 callout, chunk size up to 100. If each needs 3 callouts, chunk size up to 33.

Choose chunk size based on callouts

// 50 callouts per chunk (1 per record) — well under the 100 cap
Database.executeBatch(new SyncBatch(), 50);

Default 200 will fail for any per-record callout. Always lower the chunk size for callout-heavy batches.

What if you call out from start or finish?

Database.AllowsCallouts enables callouts from any of the three methods — start, execute, or finish. But callouts from start are rare and discouraged because it runs once: any failure stops the whole job. finish callouts are slightly more common (e.g., notifying an external system that the job is done).

Persist results, don’t return them

Callouts in batches are fire-and-forward. To track results, update fields on the records themselves (as in the example) or write to a custom log object:

insert new Sync_Log__c(
    Account__c = a.Id,
    Response_Code__c = resp.getStatusCode(),
    Response_Body__c = resp.getBody()?.left(32000)
);

Common interview follow-ups

  • What does Database.AllowsCallouts actually do? — Just declares intent. It’s a marker interface (no methods to implement).
  • Can I use Named Credentials? — Yes, and you should. Avoid hardcoded URLs/keys.
  • Does the same rule apply to Queueable? — Yes. Queueable uses Database.AllowsCallouts too.
  • Will callouts in batch ever be allowed without the interface? — Not as of Spring ‘26. The opt-in pattern is here to stay.

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