SfSkills Domains Docs GitHub

Home / apex / apex-future-method-patterns

apex-future-method-patterns

@future methods: primitive-only parameters, callout=true, no chaining, 50 per transaction, error handling. When to prefer Queueable/Batch instead per async-selection decision tree. NOT for Queueable job design — use apex/apex-queueable-patterns. NOT for Batch Apex — use apex/batch-apex-patterns.

Open the skill folder on GitHub


Apex Future Method Patterns

Activate when @future is the proposed async mechanism — or when reviewing existing @future methods for modernization. @future is the oldest async tool on the platform and has hard restrictions (primitive parameters only, no chaining, limited visibility) that make Queueable the better choice for most new work. Consult standards/decision-trees/async-selection.md before committing.

Before Starting

Core Concepts

Parameter restrictions

Only primitives (Id, String, Integer, etc.) and collections of primitives. No SObjects, no Apex objects. Workaround: pass Set<Id> and re-query; or JSON.serialize(records) + JSON.deserialize inside.

callout=true

Annotation: @future(callout=true). Required for any HTTP callout. The method becomes a "future callout" and is counted separately in limits.

No @future-to-@future chaining

A @future cannot call another @future: the allocation is "0 in batch and future contexts", so the platform throws System.AsyncException: Future method cannot be called from a future or batch method. Queueable can chain Queueable; @future cannot chain @future. This is the main modernization driver.

It CAN, however, enqueue one Queueable — the System.enqueueJob allocation is 50 synchronously and 1 in an asynchronous context. So @future → single Queueable is a legal (if awkward) escape hatch.

The reverse direction is allowed — Queueable CAN call @future

A Queueable.execute() has an allocation of 50 @future calls per Apex invocation: the limit reads "0 in batch and future contexts; 50 in queueable context". Calling @future from a Queueable does not throw. Salesforce advises against it on design grounds — "having multiple future methods fan out from a queueable job isn't a recommended practice as it can rapidly add many future methods to the asynchronous queue" — but it is not a platform restriction. Treat a @future call inside a Queueable as a code-review discussion, never as a defect to be "fixed" because it supposedly cannot compile.

Governor limits

Max 50 @future calls per transaction. Max 250k methods per 24h per license. Failures retry up to 5 times with exponential backoff (platform-managed).

Static method only

@future must be on a public static void method. Cannot be on instance methods.

Common Patterns

Pattern: Future from trigger for callout

public class CalloutService {
    @future(callout=true)
    public static void pushChanges(Set<Id> accountIds) {
        for (Account a : [SELECT Id, Name FROM Account WHERE Id IN :accountIds]) {
            // HTTP callout
        }
    }
}

Pattern: Avoid future — use Queueable instead

When new code needs async DML without callouts, prefer Queueable: supports chaining, richer parameters, better monitoring.

Pattern: Future → Queueable conversion during refactor

When modernizing, wrap the old @future body inside a Queueable execute(...) method; change callers to System.enqueueJob(new X(...)).

Decision Guidance

SituationMechanism
Callout from trigger (quick win)@future(callout=true)
Async DML, might chainQueueable
>50 async starts per transactionBatch Apex
Need to pass SObjects as-isQueueable (SObjects allowed)
Existing @future working fineKeep (don't modernize for modernization's sake)
  1. Consult standards/decision-trees/async-selection.md to confirm @future is right.
  2. Shape parameters as primitives or collections of primitives (Set<Id> preferred).
  3. Add callout=true if making HTTP calls.
  4. Handle exceptions inside the future — uncaught throws still count against retries.
  5. Monitor via Apex Jobs (Setup → Apex Jobs); failures surface with "Future" type.
  6. Bulk-safe: if caller might issue >50 futures, batch Ids into chunks or switch to Batch Apex.
  7. Document why @future was chosen over Queueable.

Review Checklist

Salesforce-Specific Gotchas

  1. Cannot call @future from another @future or batch/scheduled Apex. Throws AsyncException.
  2. Calls from test methods don't execute unless wrapped in Test.startTest() / Test.stopTest().
  3. Test.isRunningTest() inside future returns true but the database state is test-isolated. Real callouts still need mocking.

Output Artifacts

ArtifactDescription
Decision record@future vs Queueable, rationale
Future method templatePrimitive-param + re-query pattern
Monitoring runbookApex Jobs + error-log flow

Package files