Why Asynchronous Apex is Essential
Modern enterprise applications often require operations that exceed the synchronous limits of Salesforce—like processing large datasets, integrating with third-party APIs, or chaining background jobs. Asynchronous Apex enables background processing without blocking the user or exceeding governor limits.
Benefits include:
- Handling high-volume record updates
- Performing long-running HTTP callouts
- Decoupling user actions from server logic
- Ensuring scheduled execution of business tasks
Quick Comparison of All Apex Async Types
Async Type | Best For | Supports Callouts | Chaining | Gov Limits (24h) |
---|---|---|---|---|
@future | Lightweight fire-and-forget tasks | ✅ (if callout=true) | ❌ | 250k or 200 per license |
Queueable | Complex logic, chaining, monitoring | ✅ | ✅ (1 level) | 250k |
Batch | Processing >50K records in chunks | ✅ | Finish-to-next | 5 concurrent, 250k daily |
Scheduled | CRON-based triggers (nightly jobs, etc.) | ✅ | ✅ (runs queue/batch) | 100 concurrent |
Continuation | Long-running callouts (VF only) | ✅ | ❌ | Subject to VF limits |
Platform Events | Decoupled, reactive triggers | ✅ | ❌ | Based on event limits |
Future Methods
Use Case: One-off fire-and-forget tasks like logging or sending emails
Example:
apexCopyEdit@future(callout=true)
public static void sendLog(String message) {
// Post to external system
}
Limitations:
- No return values
- No chaining
- Max 50 future calls per transaction
Queueable Apex
Use Case: Complex logic, supports callouts, better than Future methods
apexCopyEditpublic class AccountSyncJob implements Queueable {
public void execute(QueueableContext context) {
// Execute sync logic
}
}
System.enqueueJob(new AccountSyncJob());
Chaining:
apexCopyEditpublic class FirstJob implements Queueable {
public void execute(QueueableContext context) {
System.enqueueJob(new SecondJob());
}
}
Batch Apex
Use Case: Processing millions of records with high reliability
apexCopyEditglobal class UpdateAccountsBatch implements Database.Batchable<SObject> {
global Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator('SELECT Id FROM Account');
}
global void execute(Database.BatchableContext bc, List<Account> scope) {
// Update logic here
}
global void finish(Database.BatchableContext bc) {
// Notify or chain next batch
}
}
Best For:
- Data cleansing
- Mass field recalculation
- Archival processes
Scheduled Apex
Use Case: Time-based triggers (e.g., nightly report generation)
apexCopyEditglobal class DailyTask implements Schedulable {
global void execute(SchedulableContext ctx) {
System.enqueueJob(new NightlyQueueJob());
}
}
apexCopyEditSystem.schedule('Run at midnight', '0 0 0 * * ?', new DailyTask());
Continuation for Long-Running Callouts
Use Case: Visualforce + Long-polling APIs
apexCopyEditpublic Continuation getResults() {
Continuation cont = new Continuation(60);
HttpRequest req = new HttpRequest();
req.setEndpoint('https://slowapi.com/ping');
cont.addHttpRequest(req);
return cont;
}
Note: Not usable in LWC or Aura
Platform Events for Reactive Async Processing
Use Case: Decoupled communication across org or external systems
apexCopyEdittrigger EventListener on CustomEvent__e (after insert) {
for (CustomEvent__e e : Trigger.New) {
// Trigger async logic
}
}
Real-World Scenarios and What to Use When
Scenario | Best Async Tool |
---|---|
Sending email notifications | @future or Queueable |
Sync with external system | Queueable (supports callout + chaining) |
Record mass update (>50k) | Batch Apex |
Daily data export | Scheduled + Batch |
Cross-module decoupled action | Platform Events |
Visualforce + callout > 5s | Continuation |
Apex Async Error Handling & Recovery
- Wrap logic in
try/catch
blocks - Log exceptions to a custom object (
Async_Error__c
) - Use retry flags to prevent loops
- Alert admins via platform events or custom notification logic
Monitoring Async Jobs in Salesforce
Tool | What It Shows |
---|---|
Apex Jobs Tab | Status, job type, submitted/finished |
Developer Console | Logs, execution order |
Workbench | Query AsyncApexJob object |
System.enqueueJob() | Returns JobId for tracking |
Advanced Tips for Chaining & Orchestration
- Queueable-to-Queueable: Chain in
execute()
- Batch-to-Queueable: Call Queueable in
finish()
- Use custom Async Manager class to track orchestration
apexCopyEditpublic void execute(QueueableContext context) {
if (condition) {
System.enqueueJob(new NextStep());
}
}
Apex Governor Limits for Async Processing
Limit | Value |
---|---|
Max Future Calls/Txn | 50 |
Max Queueable Jobs/Txn | 50 |
Max Batch Jobs Concurrent | 5 |
Max Scheduled Apex Jobs | 100 |
Total Async Apex Calls/24h | 250,000 or 200/user license |
Max Callouts per Async Txn | 10 |
Security Considerations for Async Apex
- Avoid DML without checking CRUD/FLS
- Use
with sharing
to enforce security context - Async jobs run in system context by default
Async Apex Design Patterns
- Fire-and-Forget: Future, basic Queueable
- Observer Pattern: Platform Events
- Orchestrator: Queueables with shared context
- Retry Pattern: Queueables with failure counter logic
FAQs on Salesforce Apex Async
Q: Can I return data from Future or Queueable?
A: No. All async methods are void—use a custom object or Platform Event to persist results.
Q: Can I use async within triggers?
A: Yes, but avoid recursive calls and respect limits.
Q: Best way to monitor chained jobs?
A: Track AsyncApexJob
status via JobId returned by System.enqueueJob()
.
Conclusion
Salesforce offers a rich suite of asynchronous processing tools. Understanding their strengths, limitations, and use cases enables developers to build scalable, resilient, and efficient applications.
Recommendation:
- Use Queueable for most custom async logic
- Use Batch for large volumes
- Use Scheduled for automation
- Use Platform Events for decoupled triggers
- Avoid Future, unless extremely lightweight