Asynchronous Apex
Future, Queueable, Batch and Schedulable Apex — when to use each, with examples and limits.
Developer 2 min readAsyncFutureQueueableBatchSchedulable
Async Apex runs later, in its own transaction, with higher limits (200 SOQL, 60 s CPU, 12 MB heap).
Comparison
@future | Queueable | Batch | Schedulable | |
|---|---|---|---|---|
| Start | Call the method | System.enqueueJob(job) | Database.executeBatch(job, size) | System.schedule(name, cron, job) |
| Parameters | Primitives / collections of primitives only | Any (sObjects, objects) | Query or Iterable | — |
| Job Id / monitoring | ❌ | ✅ AsyncApexJob | ✅ | ✅ CronTrigger |
| Chaining | ❌ | ✅ enqueue from execute | ✅ start another in finish | Launches other jobs |
| Best for | Simple fire-and-forget, callouts from triggers | Most async work | Millions of records | Time-based runs |
@future
apex
public class GeoService {
@future(callout=true)
public static void geocode(Set<Id> accountIds) {
// callout + update; can't take sObjects as parameters
}
}
Queueable
apex
public class SyncContactsJob implements Queueable, Database.AllowsCallouts {
private List<Contact> contacts;
public SyncContactsJob(List<Contact> contacts) { this.contacts = contacts; }
public void execute(QueueableContext ctx) {
// work with complex types, then optionally chain:
// System.enqueueJob(new NextJob());
}
}
Id jobId = System.enqueueJob(new SyncContactsJob(newContacts));
Batch Apex
apex
public class DeactivateStaleAccounts implements Database.Batchable<SObject>, Database.Stateful {
private Integer processed = 0;
public Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator(
'SELECT Id FROM Account WHERE LastActivityDate < LAST_N_DAYS:365'
);
}
public void execute(Database.BatchableContext bc, List<Account> scope) {
for (Account a : scope) a.Active__c = 'No';
update scope;
processed += scope.size();
}
public void finish(Database.BatchableContext bc) {
System.debug('Deactivated ' + processed + ' accounts');
}
}
Database.executeBatch(new DeactivateStaleAccounts(), 200); // scope 1–2,000
QueryLocatorcan process up to 50 million records.- Each
executecall is its own transaction with fresh limits. Database.Statefulkeeps instance variables between chunks.
Schedulable
apex
public class NightlyCleanup implements Schedulable {
public void execute(SchedulableContext sc) {
Database.executeBatch(new DeactivateStaleAccounts());
}
}
// Every day at 2 AM:
System.schedule('Nightly cleanup', '0 0 2 * * ?', new NightlyCleanup());
Testing async code
apex
Test.startTest();
System.enqueueJob(new SyncContactsJob(cons));
Test.stopTest(); // async work runs synchronously here, then assert
Interview questions
- Why can't
@futuremethods take sObjects? → The record may change before the method runs; pass Ids and re-query. - Queueable vs Future? → Queueable: complex params, job Id, chaining.
- How many records can batch Apex process? → Up to 50 million with a QueryLocator.
- How do you test async Apex? → Wrap in
Test.startTest()/Test.stopTest().