You cannot call a future from a future, but you can enqueue a Queueable from a future. That’s the standard workaround for any “I need a second async step” scenario.
The forbidden pattern
@future
public static void step1(List<Id> ids) {
// ... work
step2(ids); // AsyncException: future cannot be called from a future
}
@future
public static void step2(List<Id> ids) { }
The fix — enqueue a Queueable
public class Step2Queueable implements Queueable {
private List<Id> ids;
public Step2Queueable(List<Id> ids) { this.ids = ids; }
public void execute(QueueableContext ctx) {
// ... step 2 work
}
}
public class StepRunner {
@future
public static void step1(List<Id> ids) {
// ... step 1 work
System.enqueueJob(new Step2Queueable(ids));
}
}
The future runs its work, enqueues the next stage as Queueable, and exits. The Queueable runs in a new async transaction with fresh governor limits.
Why this is allowed
Queueable was introduced precisely to fix the chaining gap. From any async or sync context, you can call System.enqueueJob(new MyQueueable()) and the platform schedules it. The future-to-future ban exists in the future API alone, not in Queueable.
| From | Can enqueue Queueable? |
|---|---|
| Sync code | Yes |
@future | Yes |
Batch finish | Yes |
Batch execute | Yes (one per chunk) |
Queueable execute | Yes (one chained per execute) |
Schedulable execute | Yes |
Better: just use Queueable end-to-end
If you have two async stages, both should usually be Queueable. The chaining is natural:
public class Step1 implements Queueable {
public void execute(QueueableContext ctx) {
// ... step 1 work
System.enqueueJob(new Step2());
}
}
public class Step2 implements Queueable {
public void execute(QueueableContext ctx) {
// ... step 2 work — can also chain a Step3
}
}
Queueable accepts complex parameters (sObjects, custom classes), returns a JobId, and can chain. Future has none of those advantages.
When future is still useful
The one case where you’d keep a future and enqueue a Queueable: if your existing trigger framework is built around future methods (legacy code) and you want to add a second async step without rewriting the framework. The future-then-Queueable pattern lets you extend without disturbing the existing call sites.
Common interview follow-ups
- Does the Queueable have its own governor limits? — Yes. Each async transaction gets a fresh set.
- Will the Queueable see DML from the future? — Yes — by the time it runs, the future’s transaction has committed.
- Can I chain Queueable indefinitely? — In production, yes. (Sandboxes / Developer Edition cap at 5 levels deep.)
Verified against: Apex Developer Guide — Queueable Apex. Last reviewed 2026-05-17.