Choosing Between @future and Queueable Apex for Asynchronous Processing
When off‑loading logic in Salesforce, decide between @future and Queueable Apex. This guide outlines constraints, compares features in a table, explains trade‑offs, and shows concrete code examples with validation steps.
30 Oct 2025, 07:58 UTC

Decision Context
When you need to off‑load heavy or time‑consuming logic from a user‑initiated transaction, Salesforce offers two primary asynchronous patterns: @future methods and Queueable Apex. Both run in a separate thread, but they differ in flexibility, error handling, and how they can be chained or monitored.
Decision
Use Queueable Apex when:
- The job requires multiple steps, chaining, or complex business logic.
- You need to monitor job status, retry on failure, or handle exceptions gracefully.
- The operation may involve more than one Apex class or requires passing objects.
Use @future when:
- The task is a single, simple method call (e.g., send a notification).
- There is no need for chaining or detailed monitoring.
- Code brevity and low overhead are priorities.
Constraints
- Both approaches support a maximum of 50,000 records per call.
- Queueable jobs can be chained up to 5 levels deep.
- Both are subject to the same governor limits: 50 DML statements and 10,000 SOQL queries per execution.
- Only Queueable Apex can return a value (via
Apexableinterface) and can be passed complex objects.
Compact Comparison Table
| Feature | @future | Queueable |
|---|---|---|
| Chaining | No | Yes (max 5 levels) |
| Max batch size | 50,000 records | 50,000 records |
| Governor limits | 50 DML, 10,000 SOQL | 50 DML, 10,000 SOQL |
| Return type | void | Apexable (can return a value) |
| Monitoring | Limited (via Apex Jobs but no detailed status) | Detailed (Apex Jobs, job ID, status, retries) |
| Boilerplate code | Minimal | More verbose (implements Queueable) |
| Error handling | Basic (caught by Apex job failure) | Custom onError handling via Database.Stateful |
Trade‑offs
- @future is straightforward to invoke:
@future(callout=true)and a static method. It incurs less overhead but cannot chain, making multi‑step flows difficult. - Queueable requires implementing the
Queueableinterface, but it provides a job ID that can be tracked, supports chaining, and allows passing complex objects. The added boilerplate can be mitigated with helper classes. - Both share the same governor limits, so performance differences stem mainly from the ability to chain and monitor.
- In environments with high concurrency, Queueable jobs can be queued more aggressively, but if you only need a quick fire‑and‑forget action, @future is lighter.
Concrete Implementation – Queueable Apex
Below is a minimal queueable job that processes a list of Account records and chains to a second job.
public class AccountProcessor implements Queueable, Database.Stateful {
private List<Id> accountIds;
public AccountProcessor(List<Id> ids) {
accountIds = ids;
}
public void execute(QueueableContext ctx) {
List<Account> accounts = [SELECT Id, Name FROM Account WHERE Id IN :accountIds LIMIT 2000];
for (Account a : accounts) {
a.Name = a.Name + ' - processed';
}
update accounts;
// Chain to a second job if needed
if (accounts.size() > 0) {
System.enqueueJob(new FollowUpJob(accounts));
}
}
}
public class FollowUpJob implements Queueable {
private List<Account> accounts;
public FollowUpJob(List<Account> accs) {
accounts = accs;
}
public void execute(QueueableContext ctx) {
// Additional logic here
}
}
To start the job from a trigger or controller:
List<Id> ids = new List<Id>{/* populate with account IDs */};
System.enqueueJob(new AccountProcessor(ids));
Concrete Implementation – @future
For a simple notification, an @future method may suffice:
public class NotificationService {
@future(callout=true)
public static void sendAsync(String endpoint, String payload) {
HttpRequest req = new HttpRequest();
req.setEndpoint(endpoint);
req.setMethod('POST');
req.setBody(payload);
Http http = new Http();
HttpResponse res = http.send(req);
// No return value; errors cause job failure
}
}
Invoke from a trigger:
NotificationService.sendAsync('https://example.com/api', '{"msg":"Hello"}');
Validation Steps
- Queueable: After enqueuing, navigate to
Setup > Monitoring > Apex Jobsand verify the job status and any chained jobs. UseTest.startTest()andTest.stopTest()in unit tests to ensure the job runs and updates records. - @future: In a test method, call the @future method within
Test.startTest()andTest.stopTest(), then assert that the external call was made (e.g., by mockingHttpCalloutMock) and that no records were altered unexpectedly.
Limitations & Practical Checks
- Both methods cannot be called from a trigger that is already executing asynchronously (e.g., another @future or Queueable). Ensure the call originates from a synchronous transaction.
- Queueable jobs are subject to the same 5‑level chain limit; exceeding it results in a
System.LimitException. - For large data volumes, consider
Batch ApexorPlatform Eventsinstead of these patterns. - Always monitor Apex Jobs in production to detect failures and retries.
Conclusion
When the asynchronous logic is simple and you need minimal code, @future is the lightweight choice. For multi‑step processes, error handling, and job monitoring, Queueable Apex offers the necessary flexibility. Evaluate your use case against the constraints above to pick the right pattern.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.