Skip to main content

SF-0347 · Scenario · Medium

When to use asynchronous apex?

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

Reach for asynchronous Apex when the work doesn’t need to complete inside the user’s request and would either annoy the user, exceed sync governor limits, or fail outright in a sync context. The four canonical reasons: callouts from triggers, long-running jobs, large data volumes, and scheduled work.

The four classic triggers for going async

1. Callouts from triggers

Synchronous HTTP from a trigger throws CalloutException — the platform explicitly disallows it. Anything that requires an outbound API call from a trigger must go through @future(callout=true) or a Queueable implementing Database.AllowsCallouts.

trigger AccountTrigger on Account (after insert) {
    StripeService.createCustomers(Trigger.newMap.keySet());
}

public class StripeService {
    @future(callout=true)
    public static void createCustomers(Set<Id> ids) { /* ... */ }
}

2. Work that exceeds sync governor limits

Synchronous Apex gets 10 seconds of CPU, 100 SOQL queries, 150 DML statements, 6 MB heap. Real workloads — re-rating millions of contacts, recomputing roll-ups across years of opportunities — blow past these instantly.

Batch Apex gets a fresh quota on every execute() chunk. You can process 50 million records by paying the limit once per 200-record batch instead of once for the whole dataset.

3. Long user-facing operations

If a button click takes 8 seconds to import a CSV and update related records, the user’s browser is locked up for 8 seconds. Push the work to a Queueable, give the user a “Job submitted — we’ll notify you when it’s done” toast, and return control immediately.

4. Scheduled / recurring jobs

Anything that should run on a clock — nightly sync, weekly digest, monthly archive — needs Schedulable Apex. The sync world has no concept of “run this Apex at 2 AM.”

A more complete decision table

ScenarioReach for
HTTP callout from a trigger@future(callout=true) or Queueable + Database.AllowsCallouts
Process > 10,000 recordsBatch Apex
Process > 50 million recordsBatch Apex with Database.QueryLocator
User-initiated job that takes > 2 secondsQueueable
Chain dependent jobsQueueable (one job enqueues the next)
Run on a scheduleSchedulable, often calling Batch or Queueable
Decouple integration from savePlatform Event + Apex trigger on the event
Email after saveThe after trigger; Messaging.sendEmail itself runs in commit phase

When NOT to use async

  • The user needs to see the result before their next click. Async runs in a separate transaction — by definition the user has already moved on.
  • The work fits comfortably in sync limits and finishes in under a second. Async has overhead and isn’t free.
  • You need transactional consistency with the trigger’s DML. Async runs after the commit; it sees the post-commit database and cannot roll back the original transaction.

Common interview follow-ups

  • Why does Salesforce ban sync callouts from triggers? — Multi-tenancy. A slow callout would hold the database transaction open and starve other users.
  • What’s the latency before async runs? — Usually seconds, sometimes minutes under load. No SLA. Design for “eventually” not “immediately.”
  • Can I see the result of an async job? — Track JobId in AsyncApexJob (or query it directly). Queueable returns the JobId synchronously when you enqueue it.

Verified against: Apex Developer Guide — Asynchronous Apex Overview, Execution Governors and Limits. Last reviewed 2026-05-17.