@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.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add PranavNagrecha/AwesomeSalesforceSkills --skill apex-future-method-patterns --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Apex Future Method Patterns?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/pranavnagrecha-apex-future-method-patterns)More formats (shields.io, HTML) on the badges page.
---
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 job design — use apex/apex-queueable-patterns. NOT for Batch Apex — use apex/batch-apex-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"
- "future method cannot chain no sobject parameter"
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.1.0
author: Pranav Nagrecha
updated: 2026-05-15
---
# 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.** `@future` accepts only primitive types, lists/sets/maps of primitives. Pass `Set<Id>` or JSON-serialized SObject blobs.
- **Mark `callout=true` if making HTTP callouts.** Without it, callouts throw `CalloutException: 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 `@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
| 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
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
- [ ] Parameters are primitives only
- [ ] `callout=true` present if HTTP callouts made
- [ ] Method is `public static void`
- [ ] Caller respects 50-future-per-transaction limit
- [ ] No chained `@future` calls (not possible)
- [ ] Exception handling inside future method
- [ ] Apex Jobs monitoring covered in runbook
- [ ] Decision to use `@future` documented per async decision tree
## 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
| 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 async
- `apex/apex-batch-patterns` — high-volume async
- `standards/decision-trees/async-selection` — choosing async mechanism
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!