Run Sequential Background Tasks with Queueable Apex
Enqueue asynchronous Apex jobs that accept sObjects and chain follow-up work, then monitor results in AsyncApexJob.
13 Mar 2026, 21:58 UTC

Problem and takeaway
You need to update records after a transaction commits, but the work is too long for a trigger and must happen in a specific order. @future methods cannot accept sObjects, cannot call other @future methods, and give you no reliable way to sequence work. Queueable Apex solves this by letting you enqueue a job that runs after the current transaction, accept sObjects in its constructor, and enqueue another job from inside its execute method to create a chain.
Takeaway: use Queueable Apex when you need asynchronous, sequenced processing that can carry sObject data between steps.
Desired outcome
Enqueue an Apex job that updates an Account asynchronously, then chains a second job to update related contacts, with both jobs visible in the AsyncApexJob table.
Prerequisites
- Salesforce org with Apex enabled, API version 34.0 or later.
- Permission to create Apex classes, typically Modify All Data or the Author Apex permission.
- Developer Console, VS Code with Salesforce extensions, or the Salesforce CLI for running anonymous Apex.
Procedure
1. Create the Queueable class
A Queueable class implements the Queueable interface and defines an execute(QueueableContext) method. QueueableContext provides a job ID you can use to monitor or abort the job.
public class AccountQueueableJob implements Queueable {
private final Account target;
public AccountQueueableJob(Account target) {
this.target = target;
}
public void execute(QueueableContext context) {
if (target == null) return;
target.Description = 'Processed by Queueable job ' + context.getJobId();
update target;
// Chain a second job so work runs sequentially after this job finishes.
System.enqueueJob(new ContactQueueableJob(target.Id));
}
}
2. Enqueue the first job
Run anonymous Apex in the Developer Console or with the Salesforce CLI. Replace 001XXXXXXXXXXXX with a real Account ID from your org.
Account acc = [SELECT Id, Name FROM Account WHERE Id = '001XXXXXXXXXXXX' LIMIT 1];
System.enqueueJob(new AccountQueueableJob(acc));
Where to run: Developer Console > Debug > Open Execute Anonymous Window, or Salesforce CLI. Permissions: Modify All Data or equivalent. Risk: enqueuing starts asynchronous work immediately, and you cannot edit the running job's code after submission.
3. Define the chained job
public class ContactQueueableJob implements Queueable {
private final Id accountId;
public ContactQueueableJob(Id accountId) {
this.accountId = accountId;
}
public void execute(QueueableContext context) {
for (Contact c : [SELECT Id, Name FROM Contact WHERE AccountId = :accountId LIMIT 200]) {
c.Description = 'Updated after Account job ' + context.getJobId();
}
update c;
}
}
Expected checks
After enqueuing, confirm the job ran by querying AsyncApexJob. This object records every asynchronous Apex execution, including Queueable jobs.
SELECT Id, JobType, Status, ExtendedStatus, CreatedDate, CompletedDate
FROM AsyncApexJob
WHERE JobType = 'Queueable'
ORDER BY CreatedDate DESC
LIMIT 10
Expected result: Status equals Completed and ExtendedStatus is null for successful jobs. The JobType should be Queueable. You can also check Setup > Monitor > Jobs > Apex Jobs.
Diagnostic decision: Queueable vs. @future
| Factor | Queueable | @future |
|---|---|---|
| Accepts sObjects | Yes | No |
| Can chain another job | Yes | No |
| Job ID for monitoring | Yes, via QueueableContext | No |
| Runs after current transaction | Yes | Yes |
Recovery and rollback
Enqueuing changes org state by creating an asynchronous job. If a job is stuck in the Queued status or you enqueued it by mistake, abort it with the job ID from QueueableContext or AsyncApexJob.
Id jobId = [SELECT Id FROM AsyncApexJob WHERE JobType = 'Queueable' ORDER BY CreatedDate DESC LIMIT 1].Id;
System.abortJob(jobId);
Where to run: Anonymous Apex. Permissions: Modify All Data. Risk: aborting a job stops it permanently; any partial work already committed is not rolled back automatically.
Uncaught exceptions in a Queueable job are emailed to the user who enqueued the job and the organization default workflow user. Check those inboxes if ExtendedStatus contains an error message.
Limitations
- Each job counts against asynchronous Apex governor limits, including heap size, CPU time, and daily asynchronous job limits.
- Queueable jobs do not run immediately; timing depends on platform load.
- Chained jobs consume separate asynchronous limits, so long chains can delay execution or hit daily limits.
Practical verification
- Enqueue the job from anonymous Apex using a real Account ID.
- Query
AsyncApexJobafter a few minutes and confirmStatus = 'Completed'. - Confirm the Account description was updated and the chained Contact job appears as a second
AsyncApexJobrow.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.