@future: The Quick Fix — Async Apex
Not every async problem is a bulk problem. Sometimes you just need one operation out of the way — and @future is the simplest tool Salesforce gives you to do it.
Sometimes you don't have 50,000 records. You just have one operation that can't run right now — either because of where it's being called from, or because it needs to reach out to an external system. That's exactly the gap @future methods fill.
What is a @future Method?
@future is an annotation over a method that tells Salesforce to run it asynchronously — pushed out of the current execution context and processed in the background when resources are available. Typically used for long-running operations, callouts to external web services, or any operation you want isolated from the main transaction.
Signature
public with sharing class FutureDemo {
@future
public static void myFutureMethod() {
// your operations
}
}
Dos and Don'ts
Before writing a single line inside a @future method, know these rules cold:
✅ Must always return
void✅ Must be
static✅ Can accept primitive types —
String,Integer,Boolean,Id,List<Id>etc.❌ Cannot accept sObjects or any non-primitive type as a parameter
Why no sObjects? By the time the method is invoked and by the time it actually executes, the record could have been modified by something else. Your
@futuremethod would be holding stale data and could overwrite newer values when it runs. That's why developers passIdinstead and query the record fresh inside the method at runtime.
When Do You Actually Need It?
1. The Mixed DML Problem
This is the most common reason developers reach for @future without even realising it.
Salesforce doesn't allow you to modify a setup object and a non-setup object in the same transaction.
Setup objects:
User,UserRole,Profile,Groupand similarNon-setup objects:
Account,Contact,Leadand similar
Try it and you get:
System.DmlException: MIXED_DML_OPERATION
Without @future — this throws the error:
// Setup Object — Insert User
Profile p = [SELECT Id FROM Profile WHERE Name='Standard User' LIMIT 1];
UserRole r = [SELECT Id FROM UserRole LIMIT 1];
User u = new User(
Alias = 'testu', Email='testuser@example.com',
EmailEncodingKey='UTF-8', LastName='Testing', LanguageLocaleKey='en_US',
LocaleSidKey='en_US', ProfileId = p.Id, UserRoleId = r.Id,
TimeZoneSidKey='America/Los_Angeles',
UserName='testuser' + DateTime.now().getTime() + '@test.com'
);
insert u;
// Non-Setup Object — Insert Coupon
Promotion pr = [SELECT Id FROM Promotion LIMIT 1];
Coupon c = new Coupon(CouponCode = 'ERROR_TEST', PromotionId = pr.Id);
insert c; // BOOM — Mixed DML error
With @future — fixed:
public class FutureDemo {
@future
public static void createUser() {
Profile p = [SELECT Id FROM Profile WHERE Name='Standard User' LIMIT 1];
UserRole r = [SELECT Id FROM UserRole LIMIT 1];
User u = new User(
Alias = 'testu', Email='testuser@example.com',
EmailEncodingKey='UTF-8', LastName='Testing', LanguageLocaleKey='en_US',
LocaleSidKey='en_US', ProfileId = p.Id, UserRoleId = r.Id,
TimeZoneSidKey='America/Los_Angeles',
UserName='testuser' + DateTime.now().getTime() + '@test.com'
);
insert u;
}
}
// Non-Setup Object runs now
Promotion pr = [SELECT Id FROM Promotion LIMIT 1];
Coupon c = new Coupon(CouponCode = 'ERROR_TEST', PromotionId = pr.Id);
insert c;
// Setup Object pushed to background — separate transaction
FutureDemo.createUser();
The two DML operations now run in separate transactions. No error.
2. Callouts to External Systems
By default Salesforce doesn't allow HTTP callouts from triggers or certain other contexts. Adding callout=true to the annotation unlocks it.
@future(callout=true)
public static void syncWithExternalSystem() {
HttpRequest req = new HttpRequest();
req.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');
req.setMethod('GET');
Http http = new Http();
try {
HttpResponse res = http.send(req);
System.debug('Response: ' + res.getBody());
} catch(Exception e) {
System.debug('Callout failed: ' + e.getMessage());
}
}
Before running this — make sure the endpoint is added in Setup → Remote Site Settings. Salesforce only allows callouts to endpoints explicitly authorised there.
Limits
Three specific ones worth knowing:
You can call a
@futuremethod from a regular synchronous method — but you cannot call another async method from inside a @future method. No chaining whatsoever.During test execution,
@futuremethods don't run immediately. They behave asynchronously untilTest.stopTest()is called — at that point all queued async calls execute synchronously. Keep this in mind when writing test classes.In a 24-hour period, Salesforce allows either 250,000 @future method calls or
(number of user licences × 200)— whichever is greater.
Seeing It Run — Apex Jobs
Once it fires track it in Setup → Apex Jobs. Status will show Queued then Completed. Unlike Batch Apex there's no chunk breakdown — it's a single job entry per method call.
That's @future Methods
Simple annotation, two real use cases, one hard limit on chaining — and a very good reason never to pass sObjects as parameters.
When you hit a Mixed DML wall or need to fire a callout from a trigger, this is your first reach.


