Future Methods
By the end of this lesson, you'll be able to:
- Mark a method as asynchronous using @future
- Explain the restrictions on a @future method's parameters
- Recognize @future as the simplest, but most limited, async tool
Prerequisites: "Why Run Work Asynchronously?"
Marking a method as future
public class AccountSyncService {
@future(callout=true)
public static void syncAccountToExternalSystem(Id accountId) {
Account acc = [SELECT Id, Name FROM Account WHERE Id = :accountId];
HttpRequest request = new HttpRequest();
request.setEndpoint('callout:External_CRM/accounts');
request.setMethod('POST');
request.setBody(JSON.serialize(acc));
new Http().send(request);
}
}
public class AccountSyncService {— the class.@future(callout=true)— marks the method asynchronous;callout=trueis required specifically because the method makes an HTTP callout.public static void syncAccountToExternalSystem(Id accountId) {—static, and anIdparameter, not the full record.Account acc = [SELECT Id, Name FROM Account WHERE Id = :accountId];— fresh data, queried inside the method itself.- (blank line)
HttpRequest request = new HttpRequest();— Module 36's request-building.request.setEndpoint('callout:External_CRM/accounts');— a Named Credential endpoint.request.setMethod('POST');— sending data, not just reading.request.setBody(JSON.serialize(acc));— the Account serialized to JSON.new Http().send(request);— the callout, made possible here specifically becausecallout=truewas set.}— closes the method.}— closes the class.
@future(callout=true) marks this method to run asynchronously — callout=true is required specifically because this method makes an HTTP callout (Module 36); without it, the callout would fail. Calling AccountSyncService.syncAccountToExternalSystem(acc.Id) from a trigger returns immediately, with the actual sync happening moments later in its own transaction.
The parameter restriction
// WRONG — @future methods cannot accept sObjects or objects as parameters
@future
public static void processAccount(Account acc) { ... } // compile error
// RIGHT — pass an Id, then query fresh data inside the method
@future
public static void processAccount(Id accountId) {
Account acc = [SELECT Id, Name FROM Account WHERE Id = :accountId];
// ...
}
// WRONG — @future methods cannot accept sObjects or objects as parameters— framing the anti-pattern.@future— the annotation.public static void processAccount(Account acc) { ... } // compile error— passing a fullAccountrecord directly fails to compile.- (blank line)
// RIGHT — pass an Id, then query fresh data inside the method— framing the correct pattern.@future— the same annotation.public static void processAccount(Id accountId) {— a primitiveIdparameter instead.Account acc = [SELECT Id, Name FROM Account WHERE Id = :accountId];— fresh data, queried when the method actually runs.// ...— the rest of the method's logic.}— closes the method.
@future methods can only accept primitive types (Id, String, Integer, and collections of these) — never an sObject or a custom class directly. This is because the method's parameters get serialized and queued for later execution, and passing a full record risks it being stale by the time the method actually runs; passing just the Id and re-querying fresh data avoids that entirely.
The real limitations
@future methods must be static, cannot return a value (always void), and cannot call another @future method. These constraints are exactly why later lessons' tools (Queueable, Batchable) exist — they lift most of these restrictions, at the cost of slightly more setup.
Exercise
Write a @future method logAuditEvent(Id recordId, String eventType) that debugs both parameters.
Show hint
Use only primitive parameter types.
Future Methods Quiz
My Notes
Log in to keep private notes on this lesson.
Questions about this lesson
No questions yet — be the first to ask.
Log in to ask a question about this lesson.
Summary
@future is the simplest way to run a method asynchronously — a single annotation, with a few real restrictions that later lessons' tools don't share.