Salesforce Asynchronous Apex
A restaurant kitchen does not make the customer wait at the counter while a complex dish cooks. The cashier takes the order, hands a ticket to the kitchen, and serves the next customer. Asynchronous Apex follows the same idea. Slow or heavy work moves to the background, and the user keeps working without delay.
Synchronous and Asynchronous Compared
Synchronous Asynchronous
----------- ------------
User clicks Save User clicks Save
| |
Heavy work runs now Quick work finishes
| |
User waits... Heavy work joins a queue
| |
Screen refreshes Screen refreshes at once
Background job runs later
Background jobs receive higher limits. An asynchronous transaction can run 200 SOQL queries instead of 100 and uses a heap of 12 MB instead of 6 MB.
The Four Asynchronous Tools
| Tool | Best For | Key Feature |
|---|---|---|
| Future Method | Simple one-off tasks and callouts | Annotation @future |
| Queueable Apex | Jobs that need complex data or chaining | Accepts objects, returns a job Id |
| Batch Apex | Processing thousands or millions of records | Splits work into chunks |
| Scheduled Apex | Jobs that run at set times | Uses cron-style schedules |
Future Methods
A future method is a static method marked with @future. It accepts only simple parameters such as Ids and strings.
public class AccountUtil {
@future(callout=true)
public static void syncAccount(Set<Id> accountIds) {
// call external service for each account
}
}
Future methods cannot call other future methods, and the order of execution is not guaranteed. Modern code prefers Queueable Apex for those reasons.
Queueable Apex
A Queueable class implements the Queueable interface and holds any variables the job needs.
public class WelcomeJob implements Queueable {
private List<Id> contactIds;
public WelcomeJob(List<Id> ids) { contactIds = ids; }
public void execute(QueueableContext ctx) {
List<Contact> cons = [SELECT Id, Email FROM Contact WHERE Id IN :contactIds];
// send welcome logic here
}
}
Id jobId = System.enqueueJob(new WelcomeJob(idList));
The returned job Id lets developers monitor progress from Setup. One Queueable job can start another, which creates a chain for multi-step work.
Batch Apex
Batch Apex processes large data sets in slices. A batch class implements Database.Batchable and defines three methods.
start() --> builds the full list of records (query) | execute() --> runs once per slice (default 200 records) | (repeats for every slice) finish() --> runs once at the end (send summary email)
global class CleanupBatch implements Database.Batchable<SObject> {
global Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator('SELECT Id FROM Lead WHERE Status = \'Stale\'');
}
global void execute(Database.BatchableContext bc, List<SObject> scope) {
delete scope;
}
global void finish(Database.BatchableContext bc) {
System.debug('Cleanup complete');
}
}
Database.executeBatch(new CleanupBatch(), 200);
Each slice runs as its own transaction with fresh limits. A failure in one slice leaves the other slices intact. The second parameter of executeBatch sets the slice size, from 1 to 2,000.
Scheduled Apex
A class that implements Schedulable runs at a chosen time.
global class NightlyCleanup implements Schedulable {
global void execute(SchedulableContext sc) {
Database.executeBatch(new CleanupBatch(), 200);
}
}
System.schedule('Nightly Cleanup', '0 0 2 * * ?', new NightlyCleanup());
The cron text 0 0 2 * * ? means every day at 2:00 AM. Schedulable classes often start a batch job, which combines timing with large-volume processing.
Choosing the Right Tool
Need a quick callout after a trigger? ---------> Queueable (or future) Need to process more than 50,000 records? -----> Batch Apex Need to run every night? ----------------------> Scheduled Apex + Batch Need job steps that depend on each other? -----> Queueable chain
Monitoring Jobs
Setup offers pages named Apex Jobs and Scheduled Jobs. Admins see status, number of batches processed, and failure counts. A job that fails shows an error message that guides the fix.
Example Scenario: Welcome Email After Sign-Up
A trigger on Contact must notify an external marketing tool when a new contact arrives. The trigger collects the new Ids and passes them to a Queueable class. The Queueable class queries the contacts, sends the callout, and updates a sync status field. The user who created the contact sees the page refresh instantly, and the marketing sync finishes a few seconds later.
Contact saved
|
v
Trigger collects Ids --> System.enqueueJob()
| |
User sees success Background job runs
|
Callout + status update
What Happens When a Job Fails
An unhandled exception stops the job and records the error in the Apex Jobs page. Salesforce emails the developer who last modified the class. Wrap risky steps in try and catch blocks, write the failure to a custom log object, and decide whether the job should retry. Batch Apex helps here because only the failing slice stops, and the remaining slices continue.
Best Practices
- Pass Ids to asynchronous code and query fresh data inside the job.
- Keep batch slices small when each record needs many queries or callouts.
- Avoid starting async jobs inside loops.
- Build jobs that run safely twice, since retries can occur.
- Write test classes that wrap the call between Test.startTest and Test.stopTest so the job executes during the test.
