Skip to main content

Mastering Queueable Apex in Salesforce: Job Chaining, Callouts & Best Practices

In plain words: Queueable Apex is an asynchronous execution framework in Salesforce that lets you run resource-heavy or long-running tasks in the background. Unlike legacy @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 @future methods (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 an Id corresponding to an AsyncApexJob record, 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).
360 Queueable Apex Architecture Card:
  • Core Interface: public class MyJob implements Queueable
  • Callout Support: Implement Database.AllowsCallouts alongside Queueable to 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

Step 1: Define the Queueable Class
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;
        }
    }
}
Step 2: Enqueue the Job from a Trigger or Controller
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

Developer Trap: Infinite Chaining & Unit Test Failures
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.
Core Rule: Use Queueable Apex when processing up to a few thousand records or when passing complex data structures and chaining dependent tasks. For massive data volumes (millions of rows), prefer Batch Apex.
  • 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.