Skip to main content

SF-0354 · Scenario · Medium

Can we call a future method from another future method?

✓ Verified by Vikas Singhal · Last reviewed 5/17/2026 · Updated for Spring '26

No. Salesforce blocks the call at runtime with:

System.AsyncException: Future method cannot be called from a future or batch method.

Future methods cannot start other future methods. The same rule blocks Batch Apex from invoking @future, and blocks future methods from calling out to Batch Apex.

The forbidden pattern

public class Forbidden {
    @future
    public static void step1(List<Id> ids) {
        // ... do work
        step2(ids); // AsyncException at runtime
    }

    @future
    public static void step2(List<Id> ids) { /* ... */ }
}

The first future call works. The moment step1 calls step2, the platform throws.

Why the restriction exists

Future methods were Salesforce’s first async primitive. To prevent runaway chains that could exhaust async capacity (250,000 jobs/day), Salesforce capped chaining: one future per transaction, period. No depth, no fan-out.

When Queueable arrived in Winter ‘15, Salesforce relaxed the chaining model — but only inside Queueable’s own contract. Future stayed locked.

The two-future trap to know

CallerCalleeAllowed?
Sync code (controller, trigger)@futureYes
@future@futureNo — AsyncException
@futureQueueableYes
Batch execute@futureNo — AsyncException
Scheduled execute@futureYes (Scheduled is sync from the platform’s view)
QueueableQueueableYes (one chained per execute)
Queueable@futureYes

The workaround

Convert one (or both) future methods to Queueable. Queueable chaining is the modern path:

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
    }
}

Or call Queueable from inside the future:

@future
public static void step1(List<Id> ids) {
    // ... step 1 work
    System.enqueueJob(new Step2Queueable(ids));
}

Common interview follow-ups

  • Is the error a compile-time or runtime error? — Runtime. The compiler doesn’t trace call chains.
  • Can I unit-test this? — Yes — the AsyncException will surface inside Test.startTest()/Test.stopTest().
  • Why is Queueable allowed to chain but not future? — Different generation of the API. Queueable was designed with chaining as a first-class feature.

Verified against: Apex Developer Guide — Future Method Limitations. Last reviewed 2026-05-17.