Batch vs Queueable vs Future

When to use Batch Apex, Queueable Apex, or @future methods for async processing in Salesforce.

TL;DR

Batch = large data volumes with chunking. Queueable = complex async with chaining. Future = simple fire-and-forget for callouts and mixed DML.

The Quick Answer

Aspect@futureQueueableBatch
Best forSimple callouts, mixed DMLComplex async with stateLarge-volume processing
Max recordsNo chunking — handle your own limitsNo chunking — handle your own limitsUp to 50M (200 per chunk default)
ChainingNoYes — one child job per executionNo (but can enqueue Queueable from finish())
ParametersPrimitives only (no sObjects)Any serializable typeQueryLocator or Iterable
CalloutsYes (callout=true)Yes (Database.AllowsCallouts)Yes (Database.AllowsCallouts)
Job trackingNo job ID returnedJob ID via System.enqueueJob()Job ID, trackable in Apex Jobs
Governor limitsShared async limitsOwn transaction per executionOwn limits per execute() chunk
Concurrent limit~50 queued~50 queued5 concurrent batch jobs

When to Use @future

  • Simple callouts from trigger context — the classic use case
  • Mixed DML — setup and non-setup objects in the same transaction
  • The operation is truly fire-and-forget with no need to track completion
  • You do not need to pass sObjects or complex types

When to Use Queueable

  • Complex async logic that needs sObject parameters or state
  • Job chaining — Step 1 completes, enqueues Step 2
  • You need a job ID to monitor completion
  • Moderate record volumes (hundreds to low thousands)

When to Use Batch

  • Large data volumes — anything over 10K records that needs chunking
  • Scheduled processing — nightly syncs, data cleanup, archival
  • Stateful aggregation — counting or summarizing across chunks (Database.Stateful)
  • You need automatic retry granularity at the chunk level

Architect’s Take

Developers default to Batch because it feels safest. But Queueable is the better general-purpose choice for most async work — it accepts complex parameters, chains naturally, and returns a job ID. Reserve Batch for actual large-volume processing where chunking matters.

The @future trap: easy to write, hard to debug. No job ID, no chaining, primitives only. Use it for the two things it was designed for (callouts from triggers, mixed DML) and Queueable for everything else.