@future methods, Queueable Apex allows you to pass complex data types (such as sObjects and collections), returns a trackable AsyncApexJob ID immediately, and supports sequential job chaining to execute multi-step background processes.
Synchronous transactions in Salesforce have strict governor limits—such as CPU timeouts and heap size caps—to ensure platform resources remain shared fairly across all tenants. When your business logic requires heavy data processing, complex calculations, external REST callouts, or operations that risk slowing down the user experience, offloading the work asynchronously is critical. Queueable Apex combines the simplicity of future methods with the power of batch classes.
1. Why Choose Queueable Apex?
Salesforce provides several asynchronous tools, but Queueable Apex is the modern standard for modular background processing:
- Complex Data Types: Unlike
@futuremethods (which only accept primitive types or collections of primitives), Queueable constructors accept full sObjects, custom Apex objects, and complex nested collections. - Trackable Job IDs: Calling
System.enqueueJob()returns anIdcorresponding to anAsyncApexJobrecord, allowing you to monitor progress programmatically or via Setup. - Sequential Job Chaining: A running Queueable job can enqueue another Queueable job from inside its
execute()method, enabling multi-stage asynchronous workflows. - Higher Governor Limits: Runs in an asynchronous context, granting higher limits (such as 60,000 ms of CPU time and a 12 MB heap size).
- Core Interface:
public class MyJob implements Queueable - Callout Support: Implement
Database.AllowsCalloutsalongsideQueueableto make HTTP/REST callouts. - Enqueue Method:
Id jobId = System.enqueueJob(new MyJob()); - Limits: Up to 50 jobs enqueued per synchronous transaction (1 job enqueued from an asynchronous Queueable execution in Developer/Enterprise orgs).
2. Step-by-Step Implementation: Building a Queueable Job
Create an Apex class implementing the
Queueable interface. Pass required records directly into the class constructor.
public with sharing class UpdateAccountRevenueQueueable implements Queueable {
private List<Account> accountsToUpdate;
// Constructor accepting complex collections
public UpdateAccountRevenueQueueable(List<Account> records) {
this.accountsToUpdate = records;
}
public void execute(QueueableContext context) {
List<Account> updates = new List<Account>();
for (Account acc : accountsToUpdate) {
acc.Description = 'Revenue reviewed asynchronously via Job: ' + context.getJobId();
updates.add(acc);
}
if (!updates.isEmpty()) {
update as user updates;
}
}
}
Instantiate your class and pass it to
System.enqueueJob().
// Query records that need background processing
List<Account> targetAccounts = [SELECT Id, Name, Description
FROM Account
WHERE Industry = 'Technology'
LIMIT 100];
// Enqueue the background job and capture the Job ID
Id asyncJobId = System.enqueueJob(new UpdateAccountRevenueQueueable(targetAccounts));
System.debug('Enqueued Queueable Job with ID: ' + asyncJobId);
3. Advanced Pattern: Job Chaining & External Callouts
When you have interdependent tasks (such as creating a record, sending data to an external ERP via callout, and subsequently updating a status), you can chain jobs sequentially.
public with sharing class SyncOrderToERPQueueable implements Queueable, Database.AllowsCallouts {
private Id orderId;
public SyncOrderToERPQueueable(Id targetOrderId) {
this.orderId = targetOrderId;
}
public void execute(QueueableContext context) {
// Step 1: Perform the HTTP REST Callout
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:ERP_Named_Credential/v1/orders/' + orderId);
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody('{"status": "Processing"}');
Http http = new Http();
HttpResponse res = http.send(req);
// Step 2: Chain the next job only if the callout was successful
if (res.getStatusCode() == 200) {
if (!Test.isRunningTest()) {
// Enqueue downstream step
System.enqueueJob(new FinalizeOrderInvoiceQueueable(orderId));
}
}
}
}
4. Common Traps & Developer Best Practices
In test classes, Salesforce executes asynchronous code synchronously when wrapped inside
Test.startTest() and Test.stopTest(). However, Salesforce allows only one chained Queueable execution in unit tests. Always guard subsequent System.enqueueJob() chaining calls with if (!Test.isRunningTest()) to prevent test method failures.
- Always Implement Database.AllowsCallouts for HTTP Requests: Forgetting this marker interface will cause a runtime exception if an HTTP callout is executed inside the
execute()method. - Avoid Passing Stale sObjects: If another process updates the record while the job is waiting in the queue, writing old sObject state can overwrite recent changes. When possible, pass Record IDs and query the freshest data inside the
execute()method. - Monitor via AsyncApexJob: Query the system object to track queue status:
[SELECT Id, Status, NumberOfErrors FROM AsyncApexJob WHERE Id = :jobId].
Summary
Queueable Apex is an indispensable tool in modern Salesforce architecture. By enabling complex object parameters, immediate AsyncApexJob tracking, seamless external callouts, and sequential job chaining, developers can design high-performing, resilient applications that handle intensive workloads without impacting user response times.