Skip to main content

SF-0345 · Concept · Easy

What is asynchronous apex?

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

Asynchronous Apex is code that doesn’t run inside the user’s current request. Instead, Salesforce queues it and executes it in a separate thread — often a few seconds later, sometimes minutes later, depending on platform load. The user gets their UI back immediately; the heavy work happens behind the scenes.

The four flavours, in rough order of when you’d reach for each:

FlavourUse whenLimits compared to sync
@futureFire-and-forget tasks: a single HTTP callout, a quick update to records the user can’t see right now50 invocations per transaction, no chaining, no return value
QueueableAnything you’d do with @future plus you need to chain jobs, pass complex objects, or track status50 per transaction synchronously, only 1 chained at a time
Batch ApexOperating on a lot of records — more than fits in one transactionUp to 50 million records per job, 200 records per execute by default
SchedulableRunning something on a clock (nightly cleanup, weekly digest)One-shot scheduled, often chains to Batch or Queueable

Why it exists

Salesforce is multi-tenant. To keep one tenant’s slow code from blocking everyone else, synchronous Apex transactions get hard ceilings: 10 seconds of CPU time, 100 SOQL queries, 150 DML statements, 6 MB of heap, and so on. Real-world workflows — re-rating 2 million Contacts overnight, syncing yesterday’s Opportunities into NetSuite — would never fit inside those ceilings.

Asynchronous contexts get higher limits. Batch Apex, for example, gets 200 SOQL queries per execute and is allowed to keep CPU-time-busy across thousands of executes.

A concrete example: callout from a trigger

You can’t call an external API directly from a trigger — the synchronous request would block the user’s save, and Salesforce explicitly disallows it. The standard pattern:

public class StripeSyncService {
    @future(callout=true)
    public static void pushAccountToStripe(Set<Id> accountIds) {
        List<Account> accounts = [
            SELECT Id, Name, Stripe_Customer_Id__c
            FROM Account WHERE Id IN :accountIds
        ];
        for (Account a : accounts) {
            HttpRequest req = new HttpRequest();
            req.setEndpoint('callout:Stripe/v1/customers');
            req.setMethod('POST');
            // ... set body, send, parse response, write Stripe_Customer_Id__c
        }
    }
}

trigger AccountTrigger on Account (after insert) {
    Set<Id> ids = new Map<Id, Account>(Trigger.new).keySet();
    StripeSyncService.pushAccountToStripe(ids);
}

The @future(callout=true) annotation does two things: it queues this method for async execution, and it tells the platform “this method makes an HTTP callout, so don’t run it inside any active transaction.”

When to pick Queueable over @future

Almost always, in 2026. @future is the older mechanism — limited to primitive arguments, no monitoring, no chaining, no completion handler. Queueable was introduced to fix all of those:

public class AccountSyncJob implements Queueable, Database.AllowsCallouts {
    private List<Account> accounts;
    public AccountSyncJob(List<Account> accs) { this.accounts = accs; }

    public void execute(QueueableContext ctx) {
        // do the callout, then queue a second job if needed:
        if (moreWork()) System.enqueueJob(new AccountSyncJob(remaining));
    }
}

If you’re starting fresh on a project, write Queueable. Reserve @future for tiny tasks where you genuinely don’t need any of Queueable’s features.

Common interview follow-ups

  • “What’s the difference between @future and Queueable?” — Queueable accepts complex types, can be chained, returns a JobId you can monitor, supports callouts via Database.AllowsCallouts.
  • “When would you choose Batch over Queueable?” — When the data set is too large for one transaction. Batch’s start → execute (chunks) → finish pattern is built for million-row workloads.
  • “Can you call a @future method from another @future method?” — No. The platform blocks @future-from-@future and @future-from-Batch chains specifically.
  • “What’s the order of execution if a trigger calls a @future?” — Trigger finishes, transaction commits, @future is queued. The async work sees a database state that already reflects the trigger’s writes.

Verified against: Apex Developer Guide — Asynchronous Apex. Last reviewed 2026-05-17 for Spring ‘26 release.