The future method API forbids sObject parameters, but you almost always need to operate on records. The canonical workaround is pass a List<Id> and re-query inside the method.
The pattern
public class CaseEscalation {
// Trigger-side: capture the Ids you care about and hand them off
public static void escalate(List<Case> casesToEscalate) {
Set<Id> ids = new Set<Id>();
for (Case c : casesToEscalate) ids.add(c.Id);
escalateAsync(new List<Id>(ids));
}
@future
public static void escalateAsync(List<Id> caseIds) {
// Re-query inside the future method
List<Case> cases = [
SELECT Id, Status, Priority, OwnerId, AccountId, Account.Name
FROM Case WHERE Id IN :caseIds
];
for (Case c : cases) {
c.Priority = 'High';
c.Status = 'Escalated';
}
update cases;
}
}
The trigger captures Ids, the future re-queries. You get the freshest field values, including any changes other transactions made after the trigger fired.
Why re-query rather than pass primitives for each field?
You might think — why not just pass List<Id> plus a List<String> of new statuses? You can, but it gets messy fast:
| Approach | Problem |
|---|---|
| Pass Ids only, re-query | Clean. Always fresh data. |
| Pass parallel primitive lists (Ids + Statuses + …) | Hard to keep in sync; breaks if you add a field |
JSON-serialize a wrapper and pass as String | Works but ugly; lose compile-time safety |
Re-querying is the idiomatic answer.
Watch the SOQL limit
If your future method re-queries 50,000+ Ids, you’ll hit governor limits. Chunk the work:
public class BulkCaseProcessor {
@future
public static void processChunk(List<Id> chunk) { /* up to 10,000 safe */ }
public static void processMany(List<Id> allIds) {
Integer chunkSize = 5000;
for (Integer i = 0; i < allIds.size(); i += chunkSize) {
Integer end = Math.min(i + chunkSize, allIds.size());
List<Id> chunk = new List<Id>();
for (Integer j = i; j < end; j++) chunk.add(allIds[j]);
processChunk(chunk);
}
}
}
Each call to processChunk is its own future invocation with its own governor limits. Just remember: you can enqueue at most 50 future calls per transaction.
When the workaround isn’t enough
If your numbers are large enough that chunking gets awkward, switch to:
- Queueable Apex — accepts sObjects and complex types directly, no Id-list dance.
- Batch Apex — for >50,000 records, where you need automatic chunking across transactions.
Common interview follow-ups
- Why not pass the entire
List<Account>? — Compiler rejects it.@futureparameters must be primitives or primitive collections. - Will the future method see DML committed in the trigger? — Yes. By the time the future runs, the parent transaction is committed.
- What if a record was deleted between trigger and future? — Your re-query simply won’t return it. Handle the missing-row case gracefully.
Verified against: Apex Developer Guide — Future Methods. Last reviewed 2026-05-17.