Asynchronous Apex: Future, Queueable, Batch, Schedulable

0/9 in this phase0/41 across the roadmap

📖 Concept

Asynchronous Apex allows you to run code outside the current transaction, giving you higher governor limits and the ability to perform long-running operations. Understanding when to use each async pattern is a key senior-level skill.

The four async patterns:

  1. @future Methods — Simplest async pattern

    • Static method with @future annotation
    • Runs in its own transaction with higher limits
    • Cannot chain (no future from future)
    • Cannot track execution status
    • Parameters must be primitive types (no SObjects)
    • Use for: simple callouts, quick fire-and-forget operations
  2. Queueable Apex — Enhanced @future

    • Implements Queueable interface
    • CAN use non-primitive parameters (SObjects, custom types)
    • CAN chain jobs (one Queueable can enqueue another)
    • CAN track via AsyncApexJob
    • Returns a Job ID for monitoring
    • Use for: complex async operations, sequenced processing
  3. Batch Apex — For processing large data volumes

    • Implements Database.Batchable
    • Processes records in configurable chunks (scope size, default 200)
    • Each chunk gets its own governor limits
    • Three phases: start() → execute() × N → finish()
    • Can process millions of records
    • Use for: data cleanup, mass updates, nightly jobs
  4. Schedulable Apex — Cron-based scheduling

    • Implements Schedulable interface
    • Runs at specified times (cron expression)
    • Often combined with Batch Apex
    • Use for: nightly data processing, periodic cleanup, scheduled reports

Comparison:

Pattern      | Max Params  | Chaining  | Monitoring | Use Case
-------------|-------------|-----------|------------|-------------------
@future      | Primitives  | No        | Limited    | Simple callouts
Queueable    | Any type    | Yes (1)   | Job ID     | Complex async
Batch        | Query/List  | Chain in  | Job ID     | Large volumes
             |            | finish()  |            |
Schedulable  | N/A         | Starts    | Job ID     | Recurring jobs
             |            | other jobs|            |

💻 Code Example

codeTap to expand ⛶
1// Asynchronous Apex — All Four Patterns
2
3// 1. @future — Simple async execution
4public class FutureExample {
5
6 // Basic @future method
7 @future
8 public static void updateAccountDescription(Set<Id> accountIds) {
9 List<Account> accounts = [
10 SELECT Id, Description FROM Account WHERE Id IN :accountIds
11 ];
12 for (Account acc : accounts) {
13 acc.Description = 'Updated async at ' + Datetime.now();
14 }
15 update accounts;
16 }
17
18 // @future with callout
19 @future(callout=true)
20 public static void syncToExternalSystem(Set<Id> accountIds) {
21 List<Account> accounts = [
22 SELECT Id, Name, Industry FROM Account WHERE Id IN :accountIds
23 ];
24
25 HttpRequest req = new HttpRequest();
26 req.setEndpoint('callout:ExternalCRM/api/accounts');
27 req.setMethod('POST');
28 req.setHeader('Content-Type', 'application/json');
29 req.setBody(JSON.serialize(accounts));
30
31 HttpResponse res = new Http().send(req);
32 if (res.getStatusCode() != 200) {
33 // Log error — can't throw exception back to caller
34 System.debug(LoggingLevel.ERROR, 'Sync failed: ' + res.getBody());
35 }
36 }
37}
38
39// 2. Queueable — Advanced async with chaining
40public class QueueableExample implements Queueable, Database.AllowsCallouts {
41
42 private List<Account> accounts;
43 private Integer chainDepth;
44
45 public QueueableExample(List<Account> accounts, Integer depth) {
46 this.accounts = accounts;
47 this.chainDepth = depth;
48 }
49
50 public void execute(QueueableContext context) {
51 // Process current batch
52 for (Account acc : accounts) {
53 acc.Description = 'Processed by Queueable - Depth: ' + chainDepth;
54 }
55 update accounts;
56
57 // Chain to next job if more work remains
58 if (chainDepth < 5) { // Max 5 chains in production
59 List<Account> nextBatch = [
60 SELECT Id, Description FROM Account
61 WHERE Description = null
62 LIMIT 200
63 ];
64
65 if (!nextBatch.isEmpty()) {
66 System.enqueueJob(
67 new QueueableExample(nextBatch, chainDepth + 1)
68 );
69 }
70 }
71 }
72}
73
74// Usage:
75// Id jobId = System.enqueueJob(new QueueableExample(accounts, 1));
76// AsyncApexJob job = [SELECT Status FROM AsyncApexJob WHERE Id = :jobId];
77
78// 3. Batch Apex — Large volume processing
79public class AccountCleanupBatch implements
80 Database.Batchable<SObject>,
81 Database.Stateful,
82 Database.AllowsCallouts {
83
84 // Stateful — instance variables persist across execute() calls
85 private Integer recordsProcessed = 0;
86 private Integer errorCount = 0;
87 private List<String> errors = new List<String>();
88
89 // START — Define the data to process
90 public Database.QueryLocator start(Database.BatchableContext bc) {
91 return Database.getQueryLocator([
92 SELECT Id, Name, Industry, LastModifiedDate,
93 AnnualRevenue, Description
94 FROM Account
95 WHERE LastModifiedDate < LAST_N_DAYS:365
96 AND Industry = null
97 ]);
98 // Can return up to 50 MILLION records
99 }
100
101 // EXECUTE — Process each chunk (default 200 records)
102 public void execute(Database.BatchableContext bc, List<Account> scope) {
103 List<Account> toUpdate = new List<Account>();
104
105 for (Account acc : scope) {
106 try {
107 acc.Industry = 'Other';
108 acc.Description = 'Auto-classified on ' + Date.today();
109 toUpdate.add(acc);
110 } catch (Exception e) {
111 errorCount++;
112 errors.add(acc.Name + ': ' + e.getMessage());
113 }
114 }
115
116 Database.SaveResult[] results = Database.update(toUpdate, false);
117 for (Database.SaveResult sr : results) {
118 if (sr.isSuccess()) {
119 recordsProcessed++;
120 } else {
121 errorCount++;
122 }
123 }
124 }
125
126 // FINISH — Post-processing (send notification, chain next batch)
127 public void finish(Database.BatchableContext bc) {
128 // Send completion notification
129 Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
130 mail.setToAddresses(new List<String>{'admin@company.com'});
131 mail.setSubject('Account Cleanup Batch Complete');
132 mail.setPlainTextBody(
133 'Records processed: ' + recordsProcessed + '\n' +
134 'Errors: ' + errorCount + '\n' +
135 'Error details: ' + String.join(errors, '\n')
136 );
137 Messaging.sendEmail(new List<Messaging.SingleEmailMessage>{mail});
138
139 // Chain another batch if needed
140 // Database.executeBatch(new NextBatchJob(), 200);
141 }
142}
143
144// Execute batch:
145// Id batchId = Database.executeBatch(new AccountCleanupBatch(), 200);
146// // Monitor:
147// AsyncApexJob job = [
148// SELECT Status, NumberOfErrors, JobItemsProcessed, TotalJobItems
149// FROM AsyncApexJob WHERE Id = :batchId
150// ];
151
152// 4. Schedulable — Run at specific times
153public class WeeklyCleanupScheduler implements Schedulable {
154
155 public void execute(SchedulableContext sc) {
156 // Launch a batch job
157 Database.executeBatch(new AccountCleanupBatch(), 200);
158 }
159}
160
161// Schedule via code:
162// String cronExp = '0 0 2 ? * SAT'; // Every Saturday at 2 AM
163// System.schedule('Weekly Account Cleanup', cronExp, new WeeklyCleanupScheduler());
164
165// Schedule via Anonymous Apex:
166// WeeklyCleanupScheduler scheduler = new WeeklyCleanupScheduler();
167// String sch = '0 0 0 * * ?'; // Every day at midnight
168// System.schedule('Daily Cleanup', sch, scheduler);
169
170// CRON Expression format:
171// Seconds Minutes Hours Day_of_month Month Day_of_week Optional_year
172// 0 0 2 ? * SAT = Every Saturday at 2:00 AM

🏋️ Practice Exercise

Async Apex Practice:

  1. Write a @future method that makes a callout to an external API and logs the response
  2. Build a Queueable job that processes records in batches with chaining (max 5 chains)
  3. Create a Batch Apex job that cleans up old records across 3 objects (use Database.Stateful to track counts)
  4. Write a Schedulable class that triggers a Batch job every Sunday at 3 AM
  5. Implement a Queueable job with error handling that retries failed operations
  6. Create a batch job that makes callouts for each record (requires Database.AllowsCallouts)
  7. Write a test class for a Batch Apex job using Test.startTest() and Test.stopTest()
  8. Build a monitoring dashboard that shows the status of all AsyncApexJobs
  9. Implement a chained processing pipeline: Schedulable → Batch → Queueable → @future
  10. Design a retry framework using Platform Events and Queueable Apex

⚠️ Common Mistakes

  • @future methods cannot accept SObject parameters — only primitives (Integer, String, Set, List). Pass IDs and re-query inside the method

  • Chaining Queueable from Queueable is limited to 1 child job in production (but unlimited in tests). For complex chains, use Batch Apex or Platform Events

  • Not using Database.Stateful in Batch — without it, instance variables are reset between execute() chunks. Your counters will be wrong

  • Testing async code without Test.startTest()/Test.stopTest() — async code doesn't execute in tests without these boundaries

  • Scheduling too many concurrent Batch jobs — the limit is 5 concurrent batch jobs. Additional jobs queue but may delay processing

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for Asynchronous Apex: Future, Queueable, Batch, Schedulable. Login to unlock this feature.