Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
All errors
Apex

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

You called an `@future` method from inside another `@future`, batch, or queueable. Salesforce blocks recursive async chains because they could spiral into infinite job creation. The replacement is `Queueable` Apex, which can chain itself.

Also seen asAsyncException: Future method cannot be called·future from future·future from batch·Future method cannot be called from a future or batch method

The platform refuses to start a @future from any context that's already running asynchronously. The reason is straightforward: @future jobs are queued, and a chain of @future@future@future would let one user request spawn arbitrarily many jobs in the system queue. The hard rule prevents that whole class of runaway.

What you probably want instead

Almost every "I need to call a future from a future" turns into Queueable Apex. Queueables are the modern async primitive — they chain themselves explicitly, return a job ID you can monitor, and run within the 60-second async governor budget.

public class StepA implements Queueable {
    public void execute(QueueableContext ctx) {
        // do work
        System.enqueueJob(new StepB());   // chain into the next stage
    }
}

public class StepB implements Queueable {
    public void execute(QueueableContext ctx) {
        // next phase
    }
}

enqueueJob from inside a Queueable's execute() is allowed and returns the new job's Id. You can build long pipelines this way — A → B → C — without hitting AsyncException.

The chain limits to know

  • From Queueable: you can chain to one more Queueable (enqueueJob). In production orgs the chain is unbounded; tests are limited to two levels deep without @isTest(SeeAllData=true) tricks.
  • From Batch Apex: you can call enqueueJob from finish() to start a Queueable. You cannot call @future from any Batch method.
  • From a @future method: you cannot call any other async work, period. No @future, no enqueueJob, no Database.executeBatch.
  • From a Scheduled Apex job: you can launch Batch or Queueable from execute().

Migrating an existing @future chain

The mechanical refactor:

// Before
@future public static void step1(Set<Id> ids) {
    // ... work ...
    step2(ids);   // throws AsyncException
}
@future public static void step2(Set<Id> ids) { ... }

// After
public class Step1 implements Queueable {
    public Set<Id> ids;
    public Step1(Set<Id> ids) { this.ids = ids; }
    public void execute(QueueableContext ctx) {
        // ... work ...
        System.enqueueJob(new Step2(ids));
    }
}
public class Step2 implements Queueable { /* ... */ }

Callers that did MyClass.step1(ids) now do System.enqueueJob(new Step1(ids)). The behaviour is the same; the chain works.

When you really need @future

@future(callout=true) is still the easiest way to fire-and-forget an HTTP callout from a trigger. If you must, do the future call directly from the trigger (synchronous origin), not from another async context.

Related dictionary terms