Skip to main content

SF-0384 · Scenario · Medium

Can we call a future method from a Batch apex?

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

No. Salesforce blocks future calls from any of a Batch class’s methods (start, execute, finish) with:

System.AsyncException: Future method cannot be called from a future or batch method.

The reason is fan-out control: a batch processing millions of records in thousands of chunks could enqueue thousands of future invocations in seconds, exhausting async capacity. Salesforce simply forbids it.

What you’d write that won’t work

public class MyBatch implements Database.Batchable<sObject> {
    public Database.QueryLocator start(Database.BatchableContext ctx) {
        return Database.getQueryLocator('SELECT Id FROM Account');
    }
    public void execute(Database.BatchableContext ctx, List<Account> scope) {
        List<Id> ids = new List<Id>();
        for (Account a : scope) ids.add(a.Id);
        MyService.notifyAsync(ids); // AsyncException — chunk fails
    }
    public void finish(Database.BatchableContext ctx) {}
}

public class MyService {
    @future
    public static void notifyAsync(List<Id> ids) { }
}

Each chunk throws, NumberOfErrors climbs, no records updated.

The fix: use Queueable

Queueable doesn’t have the same restriction. You can enqueue Queueable jobs from anywhere in batch — start, execute, or finish:

public class MyBatch implements Database.Batchable<sObject> {
    public void execute(Database.BatchableContext ctx, List<Account> scope) {
        List<Id> ids = new List<Id>();
        for (Account a : scope) ids.add(a.Id);
        System.enqueueJob(new MyServiceQueueable(ids)); // allowed
    }
    public void finish(Database.BatchableContext ctx) {}
}

public class MyServiceQueueable implements Queueable {
    private List<Id> ids;
    public MyServiceQueueable(List<Id> ids) { this.ids = ids; }
    public void execute(QueueableContext ctx) { /* ... */ }
}

Or: just do the work inside execute

If the only reason you wanted a future call was to “make this async,” batch is already async. You can do the work directly in execute. If the work is a callout, add Database.AllowsCallouts:

public class MyBatch implements
    Database.Batchable<sObject>,
    Database.AllowsCallouts
{
    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);
        }
    }
}

Most “I need future-from-batch” scenarios reduce to “I just need callouts” — and the answer is Database.AllowsCallouts, not a future method.

When the future-from-batch error appears

FromCalls @futureResult
startYesAsyncException
executeYesAsyncException
finishYesAsyncException
finish calls QueueableAllowed
execute enqueues QueueableAllowed (one per chunk)

Common interview follow-ups

  • Can finish call out directly? — Yes, with Database.AllowsCallouts.
  • Can I work around with System.runAs? — No. The rule is about job context, not user context.
  • Why is Queueable allowed? — Different API generation. Queueable was designed with chaining/fan-out controls built in.

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