SfSkills Domains Docs GitHub

Home / apex / async-apex

async-apex

Use when selecting, designing, or reviewing Queueable, Batch, Future, or Schedulable Apex for callouts, large data processing, retries, or background work. Triggers: 'queueable vs batch', 'future method', 'flex queue', 'async job failed', 'schedule apex', 'move a callout out of a trigger', 'run Apex in the background'. NOT for Batchable structure or scope sizing — use apex/batch-apex-patterns. NOT for Queueable chaining — use apex/apex-queueable-patterns.

Open the skill folder on GitHub


Use this skill when synchronous Apex is the wrong execution model or when an existing async design is brittle. The job is to choose the smallest async mechanism that fits the workload, keeps it observable, and stays inside the per-transaction and org-wide async limits.

Before Starting

Questions to Ask Before Configuring

QuestionWhy it mattersWhat a good answer addsWhat proper configuration adds over just doing it
"How many records at peak, and does one transaction's 50,000-row query limit cover them?"Queueable and future jobs share the 50,000-row SOQL limit; Batch QueryLocator returns up to 50 million rowsQueueable for bounded work, Batch (or Cursors with chained Queueables) for unbounded workA job sized for the real volume, not the demo
"What starts the work: a trigger, a UI action, a schedule, or another job?"You can enqueue 50 Queueables in a synchronous transaction but only 1 from an async one, and future methods are not allowed from Batch or future contextsThe entry point and the matching enqueue patternNo LimitException the first time the job is called from a batch
"Does the work make callouts?"Queueables need Database.AllowsCallouts; synchronous callouts aren't supported from scheduled ApexThe callout-capable worker class the scheduler or trigger hands off toCallouts that run where the platform allows them
"What happens if the job fails halfway?"A rolled-back transaction discards the Queueables it enqueued, and Batch scopes are separate transactionsA Transaction Finalizer, idempotent execute, or a retry designFailures that are visible and recoverable
"How many jobs will run at once?"Only 5 batch jobs are active; the flex queue holds 100, and Database.executeBatch throws LimitException when it is fullA cap or a single dispatcher instead of one batch per eventNo dropped jobs during busy periods
"Who owns the schedule after a sandbox refresh or deployment?"Scheduled jobs aren't copied on refresh, and deploying a scheduled class fails while jobs are pendingA runbook to delete, deploy, and rescheduleReleases that do not stall on CronTrigger errors

What a proper design adds over "just make it async": the job runs in the right mechanism for its volume and entry point, its failures are observable, and it does not starve the rest of the org's async capacity.

Core Concepts

Choosing the mechanism

MechanismUse it forKey limits (Apex Developer Guide 262)
QueueableDefault background work, callouts after DML, non-primitive state, chaining50 enqueues per synchronous transaction, 1 per async transaction; one child per executing job; chain depth unlimited except 5 in Developer and Trial orgs
Batch ApexLarge, query-driven volumes with fresh limits per scopeQueryLocator up to 50 million rows; scope up to 2,000 with a QueryLocator (default 200); 5 active jobs; flex queue of 100; one start at a time
Scheduled ApexA timer that dispatches a Queueable or Batch100 scheduled jobs; synchronous limits apply; no synchronous callouts
Future methodNarrow legacy use, mixed-DML isolationStatic, void, primitive parameters only; 50 per synchronous invocation, 0 in batch and future contexts, 50 in queueable context; no guaranteed order
Apex Cursors with chained QueueablesLarge volumes without competing for the 5 batch slots50 million cursor rows per transaction; 10,000 cursors per day

Salesforce recommends Queueable over future methods: same use cases plus job IDs, non-primitive types, and chaining.

Org-wide async capacity

Asynchronous Apex method executions are limited to 250,000 per 24 hours or 200 per applicable user license, whichever is greater, shared across Batch, Queueable, scheduled Apex, and future methods. Batch checks the required capacity when Database.executeBatch is called and the start method has returned its workload, and won't start without enough capacity.

Testing

Async work queued after Test.startTest() runs synchronously at Test.stopTest(). A batch test can exercise only one execute, so size test data to one scope.

Common Patterns

Post-commit Queueable for callouts

Collect IDs in the trigger, enqueue one Queueable that implements Database.AllowsCallouts, re-query inside execute, and throw on a failed response so the AsyncApexJob records it; add a Transaction Finalizer (apex/apex-transaction-finalizers) when the job needs automatic recovery. Full class and tests in references/code-examples.md.

Batch for large, query-driven work

Database.getQueryLocator() in start, an idempotent execute with partial-success DML, and a summary in finish. Use Database.Stateful only for instance variables you need across scopes.

Scheduler as dispatcher

Schedulable.execute calls Database.executeBatch or System.enqueueJob and returns. Mark member variables transient if they must not persist between runs.

Decision Guidance

SituationRecommended ApproachReason
Trigger must call out after DML for a few hundred recordsOne Queueable with Database.AllowsCalloutsPost-commit, monitorable, callout-capable
Nightly work over millions of rowsBatch Apex dispatched by a schedulerFresh limits per scope, 50 million row locator
Many independent large jobs competing for batch slotsApex Cursors with chained QueueablesAvoids the 5-active and 100-flex-queue limits
Callout needed from a scheduleScheduler enqueues a Queueable or a callout-enabled batchSynchronous callouts aren't supported from scheduled Apex
Need to fan out from a Batch executeEnqueue one Queueable per executeAsync contexts allow one enqueue and zero future calls
Legacy future method needs sObject inputConvert to QueueableFuture parameters must be primitives
Prevent duplicate jobs for the same recordAsyncOptions with a QueueableDuplicateSignatureDuplicate enqueue throws DuplicateMessageException
  1. Size and source the work. Peak records, entry point (trigger, UI, schedule, job), callouts, and failure tolerance.
  2. Pick the mechanism from the table above; check the entry point's enqueue and future limits.
  3. Build the worker. Queueable or Batch with IDs re-queried inside, partial-success DML, and callout support where needed; start from references/code-examples.md.
  4. Add recovery. A Transaction Finalizer for Queueables, idempotent Batch scopes, and a logged summary in finish.
  5. Test with Test.startTest() and Test.stopTest(). One batch scope's worth of data; HttpCalloutMock for callouts (templates/apex/tests/MockHttpResponseGenerator.cls).
  6. Scan and deploy. Run python3 scripts/check_async_apex.py --manifest-dir force-app/main/default; delete pending scheduled jobs before deploying a scheduled class, then reschedule.

Review Checklist

Salesforce-Specific Gotchas

See references/gotchas.md. The two that surprise teams most: scheduled Apex runs with synchronous limits, and a rolled-back transaction silently discards the Queueables it enqueued.

Output Artifacts

ArtifactDescription
Async decision matrixQueueable, Batch, Future, Scheduled, or Cursors for the workload
Worker classes and testsDeployable Queueable or Batch with tests and package.xml
Async review findingsCallouts, chaining, limits, monitoring, and bulk safety
Migration planMoving future methods and heavy schedulers to Queueable or Batch

Package files