Skip to main content

SF-0410 · Scenario · Hard

If callout is not supported in scheduled apex then what is the alternate way to callout from scheduled apex?

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

Inside a Schedulable’s execute() you cannot call Http.send() directly, but you can hand the work off to another async context that does allow callouts. The three legal alternates are Queueable with Database.AllowsCallouts, @future(callout=true), and Batch Apex with Database.AllowsCallouts.

The three patterns

1. Queueable (preferred for new code)

public class SyncQueueable implements Queueable, Database.AllowsCallouts {
    private final List<Id> recordIds;
    public SyncQueueable(List<Id> ids) { this.recordIds = ids; }

    public void execute(QueueableContext qc) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint('callout:ExternalApi/sync');
        req.setMethod('POST');
        req.setBody(JSON.serialize(recordIds));
        HttpResponse res = new Http().send(req);
        // parse and persist response...
    }
}

public class NightlySyncSchedule implements Schedulable {
    public void execute(SchedulableContext sc) {
        List<Id> ids = new List<Id>();
        for (Account a : [SELECT Id FROM Account WHERE LastSyncFailed__c = true LIMIT 1000]) {
            ids.add(a.Id);
        }
        System.enqueueJob(new SyncQueueable(ids));
    }
}

Why Queueable wins: typed constructor parameters (no Id-string lists for future methods), job chaining, and the job shows up in AsyncApexJob with a real Id you can monitor.

2. @future(callout=true)

public class SyncFuture {
    @future(callout=true)
    public static void doSync(List<Id> ids) {
        HttpResponse res = new Http().send(buildRequest(ids));
        // ...
    }
    private static HttpRequest buildRequest(List<Id> ids) { /*...*/ return null; }
}

public class NightlySyncSchedule implements Schedulable {
    public void execute(SchedulableContext sc) {
        SyncFuture.doSync(new List<Id>{ /* ... */ });
    }
}

Future methods take only primitive types (and lists/sets of primitives), so you can pass Ids and Strings but not full SObject lists. Once submitted they’re fire-and-forget.

3. Batch Apex

If the work is large (more than 50 records or a few hundred callouts), use Batchable:

public class SyncBatch implements Database.Batchable<sObject>, Database.AllowsCallouts {
    public Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator('SELECT Id FROM Account WHERE LastSyncFailed__c = true');
    }
    public void execute(Database.BatchableContext bc, List<Account> scope) {
        // one callout per chunk
    }
    public void finish(Database.BatchableContext bc) { }
}

public class NightlySyncSchedule implements Schedulable {
    public void execute(SchedulableContext sc) {
        Database.executeBatch(new SyncBatch(), 50);
    }
}

Batch is the right answer when callout volume is high — Database.executeBatch with scope = 50 means you get 1 callout per 50 records and respect the 100-callout-per-transaction limit.

Which one to pick

NeedUse
Pass an SObject or a custom Apex type to the callout methodQueueable
One quick fire-and-forget callout@future(callout=true)
Tens of thousands of records to syncBatch Apex
Chain multiple callouts in sequenceQueueable (chain via System.enqueueJob in execute())

Common follow-ups

  • Why does the platform allow callouts in Queueable but not Schedulable? — Queueable runs in a fresh transaction explicitly designed for async work; Schedulable executes on the cron worker where callouts could block other tenants’ scheduled jobs.
  • Limit on queueables per Schedulable? — 1 Queueable can be enqueued per Schedulable execute() (well within the synchronous 50 cap), and that Queueable can then chain.

Verified against: Apex Developer Guide — Queueable Apex and Invoking Callouts Using Apex. Last reviewed 2026-05-17 for Spring ‘26.