name: apex-future-method-patterns description: "@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 patterns (use apex-queueable-patterns). NOT for Batch Apex (use apex-batch-patterns)." category: apex salesforce-version: "Spring '25+" well-architected-pillars:
- Reliability
- Performance tags:
- apex
- future-method
- async
- callouts
- governor-limits triggers:
- "@future method primitive parameter restriction workaround"
- "future method callout=true http from trigger"
- "can a future method call another future method chaining"
- "future method monitoring and error handling"
- "50 future method limit per transaction hit"
- "when to use future vs queueable apex" inputs:
- Current sync code needing async offload
- Operation type (callout, DML, computation)
- Parameter shape (primitives vs SObjects)
- Retry requirements outputs:
- "@future vs Queueable decision"
- Parameter-shape transformation (ids/json strings)
- Callout configuration
- Monitoring plan dependencies: [] version: 1.0.0 author: Pranav Nagrecha updated: 2026-04-21
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
- Check the async decision tree. For new work, Queueable is usually better.
- Collect the primitive parameter shape.
@futureaccepts only primitive types, lists/sets/maps of primitives. PassSet<Id>or JSON-serialized SObject blobs. - Mark
callout=trueif making HTTP callouts. Without it, callouts throwCalloutException: Callout from scheduled Apex or trigger cannot be performed.
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 chaining
A @future cannot call another @future or a Queueable. Queueable can chain Queueable (up to 5 depth); @future cannot. This is the main modernization driver.
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
| Situation | Mechanism |
|---|---|
| Callout from trigger (quick win) | @future(callout=true) |
| Async DML, might chain | Queueable |
| >50 async starts per transaction | Batch Apex |
| Need to pass SObjects as-is | Queueable (SObjects allowed) |
| Existing @future working fine | Keep (don't modernize for modernization's sake) |
Recommended Workflow
- Consult
standards/decision-trees/async-selection.mdto confirm@futureis right. - Shape parameters as primitives or collections of primitives (Set<Id> preferred).
- Add
callout=trueif making HTTP calls. - Handle exceptions inside the future — uncaught throws still count against retries.
- Monitor via Apex Jobs (Setup → Apex Jobs); failures surface with "Future" type.
- Bulk-safe: if caller might issue >50 futures, batch Ids into chunks or switch to Batch Apex.
- Document why
@futurewas chosen over Queueable.
Review Checklist
- Parameters are primitives only
-
callout=truepresent if HTTP callouts made - Method is
public static void - Caller respects 50-future-per-transaction limit
- No chained
@futurecalls (not possible) - Exception handling inside future method
- Apex Jobs monitoring covered in runbook
- Decision to use
@futuredocumented per async decision tree
Salesforce-Specific Gotchas
- Cannot call
@futurefrom another@futureor batch/scheduled Apex. ThrowsAsyncException. - Calls from test methods don't execute unless wrapped in
Test.startTest()/Test.stopTest(). - Test.isRunningTest() inside future returns true but the database state is test-isolated. Real callouts still need mocking.
Output Artifacts
| Artifact | Description |
|---|---|
| Decision record | @future vs Queueable, rationale |
| Future method template | Primitive-param + re-query pattern |
| Monitoring runbook | Apex Jobs + error-log flow |
Related Skills
apex/apex-queueable-patterns— modern asyncapex/apex-batch-patterns— high-volume asyncstandards/decision-trees/async-selection— choosing async mechanism