This guide walks through 33 asynchronous @future methods for callouts and decoupling long-running work from triggers. Each entry below includes the full problem statement, the governor-limit and best-practice constraints it's testing, and a numbered approach to solving it — then you can open the same problem in ApexArena's browser-based Apex editor and get instant pass/fail feedback against real test cases.
Easy
FutureAsync
1. Future Method HTTP Callout
Problem #5 · Salesforce Apex Coding Challenge
Problem Statement
Write a @future(callout=true) method that fires when a Case
is closed and POSTs the case details as JSON to an external REST endpoint.
Requirements
- Must use
@future(callout=true)
- Only fire when Status changes to 'Closed' (not already closed)
- Handle HTTP errors (non-200 status codes)
Approach
- 1In the trigger: call the method only when Status changes to 'Closed'.
- 2Use req.setEndpoint(), setMethod('POST'), setHeader('Content-Type','application/json'), setBody().
- 3Check res.getStatusCode() — if not 200/201, log to a custom error object.
Hard
FutureAsync
2. Queueable Chain
Problem #9 · Salesforce Apex Coding Challenge
Problem Statement
Implement a Queueable Apex chain that processes large datasets by
self-chaining — each execution enqueues the next batch until all records are processed.
This pattern overcomes the 50,000 record SOQL limit by processing in chunks.
Requirements
- Implement
Queueable and Database.AllowsCallouts
- Process records in batches of 200
- Self-enqueue when there are more records remaining
- Store offset in the job to track progress
Approach
- 1Check accounts.size() == BATCH_SIZE — if true, there may be more records. Enqueue with offset + BATCH_SIZE.
- 2Use System.enqueueJob(new AccountProcessorQueueable(offset + BATCH_SIZE)) inside execute().
- 3Limits.getQueueableJobs() lets you check if you're about to hit the 50-job limit.
Medium
AsyncTriggers
3. Platform Event Publisher
Problem #10 · Salesforce Apex Coding Challenge
Problem Statement
Publish a Platform Event (Order_Completed__e) from an Apex trigger
when an Order record's Status changes to Activated.
Write a subscriber trigger on the platform event that creates a follow-up Task
record for the account owner.
Platform Event Fields
Order_Id__c (Text)
Account_Id__c (Text)
Amount__c (Number)
Approach
- 1EventBus.publish() returns List — check isSuccess() on each.
- 2In the subscriber trigger, Trigger.new contains the published events.
- 3Tasks need Subject, WhatId (the Account), OwnerId, and ActivityDate.
Medium
AsyncGovernor
4. Schedulable Apex — Stale Case Cleanup
Problem #18 · Salesforce Apex Coding Challenge
Problem Statement
Implement a Schedulable Apex class that runs nightly to delete
Case records that have been Closed for more than
90 days.
Requirements
- Implement the
Schedulable interface
- Query Cases where
Status = 'Closed' and
ClosedDate < 90 days ago
- Delete in bulk (all records at once, not one-by-one)
- Use
Database.delete(records, false) to allow partial success
Bonus
Use System.schedule() inside a separate method to register the job
for daily execution at midnight.
Approach
- 1Query filter: WHERE Status = 'Closed' AND ClosedDate < LAST_N_DAYS:90
- 2Database.delete(list, false) allows partial success — rows that fail are skipped.
- 3System.schedule(jobName, cronExpression, schedulable) returns the job ID.
Hard
Async ApexAPI
5. Queueable Apex with Chained Callout
Problem #77 · Salesforce Apex Coding Challenge
Problem Statement
Build a Queueable Apex class that makes an HTTP callout and chains to another Queueable on success.
Requirements for ContactSyncQueueable:
- Implement
Queueable and Database.AllowsCallouts interfaces
- Constructor accepts
List<Id> contactIds and Integer retryCount
- In
execute():
- Query the Contacts by ID (Name, Email, Phone fields)
- Serialize the contacts to JSON and POST to an external endpoint
'https://api.example.com/contacts/sync'
- If response status is 200, update each Contact with
Sync_Status__c = 'Synced'
- If not 200 and retryCount < 3, enqueue a new instance of itself with retryCount + 1
- If not 200 and retryCount >= 3, set
Sync_Status__c = 'Failed' on all contacts
Constraints
- No SOQL or DML inside loops
- Use
System.enqueueJob() for chaining
Approach
- 1Use Database.AllowsCallouts interface to permit HTTP calls from Queueable
- 2JSON.serialize(contactList) converts the list to a JSON string for the POST body
- 3System.enqueueJob(new ContactSyncQueueable(contactIds, retryCount + 1)) chains the job
- 4Query contacts outside the callout: List contacts = [SELECT Id, Name, Email FROM Contact WHERE Id IN :contactIds]
Medium
Async Apex
6. @future: Async Account Rating Updater
Problem #91 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class AccountRatingAsync with a
@future method updateRatings(Set<Id> accountIds)
that queries each Account and sets:
AnnualRevenue > 1,000,000 → Rating = "Hot"
AnnualRevenue >= 100,000 → Rating = "Warm"
- Otherwise → Rating = "Cold"
Key Concept
A @future method runs asynchronously and cannot accept SObject parameters — only primitives and collections of primitives (e.g. Set<Id>).
Approach
- 1@future methods must be static and return void.
- 2Query accounts inside the method using the passed Set.
- 3Collect updated records in a list before DML.
- 4Do NOT skip records with null AnnualRevenue — null falls through to the else branch and should be rated "Cold".
Medium
Async ApexSchedulable
7. Schedulable: Weekly Lead Digest
Problem #92 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class WeeklyLeadDigest that implements
Schedulable. In its execute(SchedulableContext ctx)
method, query all Leads created in the last 7 days and send a weekly
digest email with the count to a sales address.
Requirements
- Implement the
Schedulable interface
- Query Leads with
CreatedDate >= LAST_N_DAYS:7
- Send an email via
Messaging.sendEmail with the lead count in the body
Approach
- 1Schedulable requires a void execute(SchedulableContext ctx) method.
- 2Use LAST_N_DAYS:7 as a date literal in SOQL.
- 3Use Messaging.SingleEmailMessage and Messaging.sendEmail() to dispatch the digest.
Medium
Async ApexQueueable
8. Queueable: Contact Industry Tagger
Problem #93 · Salesforce Apex Coding Challenge
Problem Statement
Write a Queueable Apex class ContactTaggerQueueable that:
- Accepts a
List<Id> of Contact IDs in its constructor
- In
execute(QueueableContext ctx), queries those Contacts
along with their Account's Industry
- Sets
Contact.Description to
"Industry: " + account.Industry
- Updates the contacts
Approach
- 1implements Queueable requires a void execute(QueueableContext ctx).
- 2Query Contacts with Account.Industry in a relationship query.
- 3Build the update list and do DML once (bulk safe).
Hard
Async ApexBatch Apex
9. Batch Apex: Close Stale Opportunities
Problem #94 · Salesforce Apex Coding Challenge
Problem Statement
Write a Batch Apex class StaleOpportunityBatch that closes
all open Opportunities whose CloseDate is in the past by
setting StageName = 'Closed Lost'.
Requirements
- Implement
Database.Batchable<SObject>
start(): return a QueryLocator for open, past-close-date Opps
execute(): update StageName to "Closed Lost"
finish(): log completion with System.debug
Approach
- 1Filter for Opportunities that are still open (IsClosed is false) and whose CloseDate has already passed.
- 2In execute(), loop scope and set opp.StageName = 'Closed Lost'.
- 3Collect changes in a list and call update once.
Medium
Async ApexBatch Apex
10. Batch Apex: Archive Stale Open Leads
Problem #148 · Salesforce Apex Coding Challenge
Problem Statement
Your marketing operations team runs a nightly job to archive leads that have been
stuck in Status = 'Open' for more than 30 days without conversion.
Write a Batch Apex class LeadArchiverBatch that:
- Queries all
Lead records where Status = 'Open'
and CreatedDate <= 30 days ago
- Sets
Status = 'Archived' on each lead in the batch
- Logs completion in
finish() using System.debug
Requirements
- Implement
Database.Batchable<SObject>
start(): use Database.getQueryLocator
execute(): bulk-safe — collect changes in a list, single DML update
finish(): System.debug a completion message
Best Practices
- Use
Date.today().addDays(-30) to compute the cutoff date.
- Never perform DML inside a loop — collect and update once per batch chunk.
- Write a test that calls
Database.executeBatch() and asserts status changed.
Approach
- 1Date cutoff = Date.today().addDays(-30); use CreatedDate <= :cutoff in SOQL.
- 2Filter on Status = 'Open' and CreatedDate <= cutoff in the start() query.
- 3In execute(), loop scope, set l.Status = 'Archived', then update scope after the loop.
- 4Test: insert leads, call executeBatch inside Test.startTest()/stopTest(), then assert Status = 'Archived'.
Medium
Async ApexQueueable
11. Queueable: Update Account Tier Based on Revenue
Problem #149 · Salesforce Apex Coding Challenge
Problem Statement
After an integration runs, your team needs to asynchronously update a custom field
Tier__c on each Account based on its AnnualRevenue.
Write a Queueable Apex class AccountTierQueueable that accepts a
Set<Id> of account IDs in its constructor and, when executed:
- Queries those accounts for
AnnualRevenue
- Sets
Tier__c based on revenue: <$50K → 'Bronze', <$250K → 'Silver',
<$1M → 'Gold', ≥$1M → 'Platinum'
- Performs a single bulk DML update
Requirements
- Implement
Queueable with void execute(QueueableContext ctx)
- Constructor must accept
Set<Id> accountIds
- One SOQL query, one DML update inside execute()
Best Practices
- Queueable is preferred over
@future when you need to pass SObject collections or chain jobs.
- Enqueue via
System.enqueueJob(new AccountTierQueueable(ids)).
- Test with
Test.startTest() / stopTest() — the job executes synchronously in tests.
Approach
- 1Query the accounts in accountIds, retrieving their AnnualRevenue.
- 2Use the same tier thresholds as AccountTierService: <50K Bronze, <250K Silver, <1M Gold, else Platinum.
- 3Build a list of accounts to update, then call update once after the loop.
- 4Test: System.enqueueJob(new AccountTierQueueable(ids)) inside startTest/stopTest, then SELECT Tier__c.
Easy
Async Apex@future
12. @future: Asynchronous Lead Score Calculator
Problem #150 · Salesforce Apex Coding Challenge
Problem Statement
When leads are inserted via an API integration, you need to asynchronously calculate
and store a numeric score on each lead so the process does not slow down the synchronous
transaction.
Write an Apex class LeadScoringService with a @future
method calculateScore(Set<Id> leadIds) that:
- Queries
LeadSource and Industry on the given leads
- Sets a custom field
Lead_Score__c using this matrix:
Scoring Matrix
- LeadSource: 'Web' = 10 | 'Phone' = 20 | 'Partner Referral' = 30 | else 5
- Industry: 'Technology' = 20 | 'Finance' = 15 | else 5
- Lead_Score__c = LeadSource score + Industry score
Best Practices
@future methods must be static, return void, and accept only primitive types or collections of primitives.
- One SOQL query and one DML update inside the method.
- Call from a trigger using
if (!System.isFuture() && !System.isBatch()) guard.
Approach
- 1@future methods cannot accept SObject parameters — only Set is correct here.
- 2Query the leads in leadIds, retrieving their LeadSource and Industry.
- 3Build a scoring map for LeadSource and Industry; sum both scores for Lead_Score__c.
- 4Collect updated leads in a list and call update once after the loop.
Easy
Async ApexFuture Methods
13. @future: Async Contact Mailing Address Sync from Account
Problem #208 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class ContactAddressSyncUtil with a
@future method syncMailingAddressFromAccount(Set<Id> accountIds)
that copies the Account's billing address to all its Contacts' mailing address fields.
Requirements
- Method must be
@future, public static void.
- Parameters must be primitives or collections of primitives — use
Set<Id>.
- Guard against null / empty input at the top.
- One SOQL for Accounts, one for Contacts — both outside any loop.
- Build a
Map<Id, Account> to avoid nested loops.
- Copy:
BillingCity → MailingCity, BillingState → MailingState,
BillingPostalCode → MailingPostalCode, BillingCountry → MailingCountry.
- One bulk DML update, guarded by
isEmpty().
Best Practices
@future methods cannot accept SObject parameters — use Set<Id>.
- Two SOQLs, zero inside loops — governor-safe.
- Use a Map keyed by Account Id for O(1) lookup in the Contact loop.
Approach
- 1@future methods only accept primitive types or collections of primitives (Set is fine).
- 2Guard: if (accountIds == null || accountIds.isEmpty()) return;
- 3Map: Map accountMap = new Map([SELECT ... FROM Account WHERE Id IN :accountIds]);
- 4Copy: c.MailingCity = acc.BillingCity; c.MailingState = acc.BillingState; etc.
Medium
Async ApexFuture Methods
14. @future(callout=true): Async Lead Enrichment via REST Callout
Problem #209 · Salesforce Apex Coding Challenge
Problem Statement
Write LeadEnrichmentUtil with a @future(callout=true) method
enrichLeadFromExternalAPI(Id leadId) that calls an external REST API
to enrich a Lead's company data.
Requirements
- Annotation:
@future(callout=true)
- Guard: return early if
leadId == null.
- Query the Lead by Id (LIMIT 1).
- Build an
HttpRequest: endpoint, method GET, Accept header, timeout 10 s.
- Call
new Http().send(req) inside a try-catch(Exception e).
- If status code is 200: parse JSON with
JSON.deserializeUntyped, set
Company, Industry, LeadSource = 'API Enriched',
then update the Lead.
- Log non-200 responses and exceptions with
System.debug.
Best Practices
- Always wrap callouts in
try-catch(Exception e) — network can fail.
- Check
getStatusCode() == 200 before trusting the body.
- Set a timeout:
req.setTimeout(10000) — prevents governor limit hits.
Approach
- 1Annotation: @future(callout=true) — callout=true is required for HTTP callouts from future methods.
- 2Timeout: req.setTimeout(10000); — prevents hitting the 120-second async timeout accidentally.
- 3Deserialise: Map payload = (Map) JSON.deserializeUntyped(res.getBody());
- 4Safe cast: (String) payload.get('company') — payload.get returns Object, must cast.
Medium
Async ApexFuture Methods
15. @future: Bulk Lead Score Reset After Campaign End
Problem #223 · Salesforce Apex Coding Challenge
Problem Statement
Write LeadScoreResetUtil with a @future method
resetLeadScoresForCampaign(Set<Id> campaignIds) that resets
Lead_Score__c to 0 for all Leads who are members of the given Campaigns.
Requirements
@future annotation; parameter must be Set<Id>.
- Guard: return early if
campaignIds is null or empty.
- Query
CampaignMember to find Lead Ids.
- Build a
Set<Id> leadIds, guard empty, then query Lead.
- Reset
Lead_Score__c = 0 on all found Leads.
- Wrap the final
update in try-catch(DmlException e).
Best Practices
- Two-step query pattern: CampaignMember → Lead Ids → Lead records.
- Check
leadIds.isEmpty() before the second SOQL to avoid empty IN clause.
- Never put SOQL or DML inside a for loop.
Approach
- 1Step 1: query CampaignMember records for the given campaigns, keeping only rows that actually have a related Lead.
- 2Step 2: collect into Set leadIds; if (leadIds.isEmpty()) return;
- 3Step 3: query the Lead records whose Id is in that leadIds set.
- 4Reset: for (Lead l : leads) { l.Lead_Score__c = 0; }
Hard
Async ApexFuture Methods
16. @future(callout=true): Account Credit Check via External API
Problem #224 · Salesforce Apex Coding Challenge
Problem Statement
Write AccountCreditCheckUtil with a @future(callout=true)
method runCreditCheck(Id accountId) that calls an external credit API
and updates Credit_Score__c and Credit_Status__c on Account.
Scoring Logic
- creditScore ≥ 700 →
Credit_Status__c = 'Approved'
- 500 – 699 →
'Review'
- < 500 →
'Declined'
Requirements
- Use
@future(callout=true); guard null accountId.
- Build POST request with Content-Type and Accept headers; timeout 15 s.
- Catch
CalloutException separately — set Credit_Status__c = 'Callout Error' and return early.
- Check status code 200; parse JSON; handle non-200 by setting
Credit_Status__c = 'API Error'.
- Wrap JSON parsing in a second
try-catch(Exception e).
Approach
- 1@future(callout=true) is required for any HTTP callout from a future method.
- 2Catch CalloutException separately: it is thrown when the network itself fails (timeout, DNS, etc.).
- 3Escape the account name: String.escapeSingleQuotes(acc.Name) — prevents JSON injection.
- 4Two try-catch blocks: one around Http.send() for CalloutException, one around JSON.deserializeUntyped() for parse errors.
Easy
Async ApexFuture Methods
17. @future: Send Welcome Email After Lead Creation
Problem #527 · Salesforce Apex Coding Challenge
Business Scenario
Whenever a new Lead is captured (e.g., from a web form), the Lead should receive an
automatic welcome email — without blocking the trigger's transaction on a mail send.
Requirements
- Write
LeadWelcomeEmailUtil with a @future method
sendWelcomeEmails(Set<Id> leadIds).
- Guard against a null or empty
leadIds at the top.
- Re-query the Leads by Id (only those with a non-null
Email) — never accept
Lead objects directly as a parameter.
- Build one
Messaging.SingleEmailMessage per Lead and send them all in a
single Messaging.sendEmail call.
Best Practices
@future methods cannot accept SObjects or List<SObject> —
only primitives and collections of primitives, hence Set<Id> plus a
re-query inside the method.
- Guarding on null/empty input avoids an unnecessary SOQL call when there's nothing to do.
- All emails are batched into one list and sent with a single
Messaging.sendEmail call, wrapped in try/catch.
Approach
- 1@future methods only accept primitives or collections of primitives — use Set leadIds.
- 2Guard: if (leadIds == null || leadIds.isEmpty()) return;
- 3Re-query the Leads inside the future method — you cannot pass SObjects into an @future method.
- 4Collect every SingleEmailMessage into a List and call Messaging.sendEmail(emails) once, wrapped in try/catch(Exception).
Easy
Async ApexFuture Methods
18. @future(callout=true): Push New Account to External CRM
Problem #528 · Salesforce Apex Coding Challenge
Business Scenario
Whenever a new Account is created, the external CRM system used by partner teams needs to
be notified via a REST callout so both systems stay in sync.
Requirements
- Write
ExternalCrmSyncUtil with a @future(callout=true) method
pushNewAccounts(Set<Id> accountIds).
- Guard against null/empty input; re-query the Accounts by Id.
- Build a single JSON payload covering all Accounts in the set and send one
POST callout to
callout:External_CRM/accounts.
- Set a request timeout and wrap the callout in
try/catch(CalloutException).
Best Practices
callout=true is required on the @future annotation any time the
method performs an HTTP callout — without it, the callout throws immediately.
- One bulk JSON payload and one callout per invocation, not one callout per Account.
- A Named Credential-style endpoint (
callout:External_CRM) keeps the real URL
and auth out of source code.
try/catch(CalloutException) ensures a network blip doesn't throw an
unhandled exception out of the async context.
Approach
- 1@future(callout=true) is required whenever the future method makes an HTTP callout.
- 2Guard: if (accountIds == null || accountIds.isEmpty()) return;
- 3Build a List
- 4req.setEndpoint('callout:External_CRM/accounts') routes through a Named Credential instead of a hardcoded URL.
Easy
Async ApexFuture Methods
19. @future(callout=true): Send SMS Alert on High-Priority Case Creation
Problem #529 · Salesforce Apex Coding Challenge
Business Scenario
When a High priority Case is created, the owning agent should get an immediate SMS alert via
a third-party SMS gateway, so they aren't relying solely on checking email or the console.
Requirements
- Write
CaseSmsNotificationUtil with a @future(callout=true)
method sendHighPriorityCaseSms(Set<Id> caseIds).
- Guard against null/empty input; re-query the Cases (and owner's
MobilePhone)
by Id, only including owners with a mobile number on file.
- Send one SMS callout per Case to
callout:SMS_Gateway/send with the case
number and subject in the message body.
- Wrap each callout in
try/catch(CalloutException) so one failed SMS doesn't
stop the rest of the Cases in the set from being notified.
Best Practices
callout=true is mandatory for any HTTP callout inside a future method.
- Filtering out owners with no
MobilePhone up front avoids attempting (and
failing) callouts that could never succeed.
- Per-Case try/catch means a single gateway timeout on one Case doesn't prevent the other
Cases in the same future invocation from being notified.
Approach
- 1@future(callout=true) is required for any callout, including to an SMS gateway.
- 2Traverse Owner.MobilePhone directly in the SOQL query — no second query needed.
- 3req.setEndpoint('callout:SMS_Gateway/send') keeps the gateway URL out of source code.
- 4Wrap each Http().send(req) call in its own try/catch(CalloutException) so one failure does not stop the loop.
Easy
Async ApexFuture Methods
20. @future(callout=true): Verify Account Billing Address
Problem #530 · Salesforce Apex Coding Challenge
Business Scenario
Before shipping physical goods, an Account's billing address should be validated against a
third-party address verification API, and the result stored back on the record.
Requirements
- Write
AddressVerificationUtil with a @future(callout=true)
method verifyBillingAddresses(Set<Id> accountIds).
- Guard against null/empty input; re-query the Accounts' billing address fields by Id.
- Call
callout:Address_Verification_API/verify once per Account with the
address fields as the JSON body.
- On a 200 response, parse the JSON and set
Address_Verified__c based on the
API's valid flag.
- Update all verified Accounts with a single bulk DML call after the callout loop.
Best Practices
callout=true is required because this future method performs HTTP callouts.
- DML is deferred until after all callouts complete and collected into one list — updating
inside the callout loop would turn one DML statement into many.
- Each callout is wrapped in its own
try/catch(CalloutException) so one
Account's address failure doesn't stop the others from being verified.
Approach
- 1@future(callout=true) is required for the address verification HTTP callout.
- 2Build the request body from the four Billing* fields via JSON.serialize on a Map.
- 3Parse a 200 response with JSON.deserializeUntyped and check the valid key to set Address_Verified__c.
- 4Collect all updates into one list and call update toUpdate; once after the loop finishes, not per Account.
Medium
Async ApexFuture Methods
21. @future(callout=true): Notify Shipping System on Order Confirmation
Problem #531 · Salesforce Apex Coding Challenge
Business Scenario
Once an Order is confirmed (Activated), the third-party shipping/fulfillment system needs to
be notified with the order's line items and shipping address so it can begin dispatch.
Requirements
- Write
ShippingNotificationUtil with a @future(callout=true)
method notifyShippingSystem(Set<Id> orderIds).
- Guard against null/empty input; re-query only
Activated Orders, plus their
OrderItem lines, in two bulk queries.
- Group line items by Order Id in memory (no per-Order query) before building the payload.
- Send a single bulk JSON payload (covering every qualifying Order) to
callout:Shipping_System/orders/dispatch.
Best Practices
callout=true is required because this method performs an HTTP callout.
- Two bulk queries (Orders, then their OrderItems) plus an in-memory grouping map avoids a
query per Order — critical since
@future methods share the same governor limits
as any other Apex transaction.
- One combined payload and one callout for the whole set, rather than one callout per Order.
Approach
- 1@future(callout=true) is required for the shipping-system callout.
- 2Query Order WHERE Id IN :orderIds AND Status = 'Activated' — only confirmed Orders should dispatch.
- 3Group OrderItem rows into a Map>> keyed by OrderId to avoid a query per Order.
- 4Build one combined payload List and send it in a single Http().send(req) call.
Medium
Async ApexFuture Methods
22. @future(callout=true): Create Invoice in External Finance System
Problem #532 · Salesforce Apex Coding Challenge
Business Scenario
After an internal Invoice__c is generated, it must also be created in an
external finance/accounting platform via a REST callout, and the returned external invoice Id
stored back for future reference. This is distinct from the purely internal
Invoice__c generation batch — here, the invoice already exists internally and is
being pushed out to a third-party system.
Requirements
- Write
ExternalFinanceInvoiceUtil with a @future(callout=true)
method createExternalInvoices(Set<Id> invoiceIds).
- Guard against null/empty input; re-query only Invoices not yet pushed
(
External_Invoice_Id__c = null).
- For each Invoice, call
callout:External_Finance_System/invoices with the
customer name and amount, and store the returned externalInvoiceId back on the record.
- Update all successfully-created Invoices in a single bulk DML call after the callout loop.
Best Practices
callout=true is required for the finance-system callout.
- Filtering to
External_Invoice_Id__c = null up front makes this method
idempotent — re-running it never double-creates an invoice that already synced.
- DML is batched into a single
update after the callout loop finishes, not
performed inside the loop.
Approach
- 1@future(callout=true) is required for the external finance system callout.
- 2Filter External_Invoice_Id__c = null in the query so already-synced Invoices are never re-pushed.
- 3Parse the callout response with JSON.deserializeUntyped and pull out externalInvoiceId.
- 4Collect updates into one list and call update toUpdate; once after the loop, not per Invoice.
Medium
Async ApexFuture Methods
23. @future: Send Customer Satisfaction Survey After Case Closure
Problem #533 · Salesforce Apex Coding Challenge
Business Scenario
Whenever a Case is closed, the associated Contact should receive a satisfaction survey email
— sent asynchronously so it never delays the transaction that closed the Case.
Requirements
- Write
CaseSurveyEmailUtil with a plain @future method
sendSurveyEmails(Set<Id> caseIds) (no callout involved — sending platform
email is not an HTTP callout).
- Guard against null/empty input; re-query only
Closed Cases with a Contact
email on file.
- Build one survey email per qualifying Case and send them all in a single
Messaging.sendEmail call.
Best Practices
- No
(callout=true) is needed here — Messaging.sendEmail is not
an HTTP callout, so the plain @future annotation is correct and sufficient.
- Filtering to
Status = 'Closed' and a non-null Contact email in the query
avoids wasting the async invocation on Cases that shouldn't get a survey.
- All survey emails are batched into one list and sent with a single call.
Approach
- 1This future method never performs an HTTP callout, so plain @future (no callout=true) is correct.
- 2Filter Status = 'Closed' AND Contact.Email != null directly in the query.
- 3Traverse Contact.Email and Contact.FirstName via the relationship — no second query needed.
- 4Collect every SingleEmailMessage into a List and call Messaging.sendEmail(emails) once.
Medium
Async ApexFuture Methods
24. @future(callout=true): Push New Contacts to Marketing Automation Platform
Problem #534 · Salesforce Apex Coding Challenge
Business Scenario
Marketing wants every new Contact with an email address automatically pushed into their
marketing automation platform for nurture campaigns, via a bulk REST callout.
Requirements
- Write
MarketingPlatformSyncUtil with a @future(callout=true)
method pushNewContacts(Set<Id> contactIds).
- Guard against null/empty input; re-query only Contacts with a non-null
Email.
- Build one JSON payload covering every qualifying Contact and send a single
bulk POST callout to
callout:Marketing_Platform/contacts/bulk-import.
- Return early (no callout) if no Contacts in the set actually qualify.
Best Practices
callout=true is required for the marketing platform callout.
- One bulk callout for the whole set of Contacts, never one callout per Contact — this
keeps the method well within the 100-callouts-per-transaction limit at any list size.
- Short-circuiting when the filtered list is empty avoids a wasted callout with an empty payload.
Approach
- 1@future(callout=true) is required for the marketing platform callout.
- 2Filter Email != null in the query — contacts without an email cannot be nurtured by the platform.
- 3Return early with contacts.isEmpty() before building the payload or attempting a callout.
- 4One JSON.serialize(payload) covering the whole filtered list, sent in a single Http().send(req) call.
Medium
Async ApexFuture Methods
25. @future(callout=true): Synchronize Product Catalog With External Catalog System
Problem #535 · Salesforce Apex Coding Challenge
Business Scenario
An e-commerce storefront maintains its own product catalog that must mirror Salesforce's
Product2 records. When Products change, they need to be pushed via an HTTP callout
to the external catalog system — unlike the internal Product_Import_Staging__c
Batch Apex import (which pulls data into Salesforce with no callout), this method
pushes data out to a third-party system.
Requirements
- Write
ExternalCatalogSyncUtil with a @future(callout=true)
method syncProductsToExternalCatalog(Set<Id> productIds).
- Guard against null/empty input; re-query the Products by Id; return early if none found.
- Send a single bulk PUT callout to
callout:External_Catalog_System/products/sync
with all Products' SKU, name, description, and active flag.
- On a 200 response, mark every synced Product
Catalog_Sync_Status__c = 'Synced';
otherwise (bad status or CalloutException) mark them 'Failed'.
Best Practices
callout=true is required for the catalog-system callout.
- One bulk callout covering every Product in the set, never one per Product.
- Always handle
CalloutException — a network failure still needs to leave the
Products in a known (Failed) state rather than an unhandled exception.
- DML happens once after the callout, covering the whole set with a single
update.
Approach
- 1@future(callout=true) is required for the external catalog system callout.
- 2Return early with products.isEmpty() if the query finds nothing to sync.
- 3Wrap Http().send(req) in try/catch(CalloutException) so a network failure still lets you mark every Product Failed.
- 4Build the toUpdate list inside both the success path and the catch block, then update it once.
Hard
Async ApexFuture Methods
26. @future(callout=true): Notify Payment Gateway After Invoice Generation
Problem #536 · Salesforce Apex Coding Challenge
Business Scenario
Once an Invoice__c is generated, the payment gateway needs to be notified so it
can register the invoice for automatic collection, returning a gateway reference id used later
to track the payment status.
Requirements
- Write
PaymentGatewayNotificationUtil with a @future(callout=true)
method registerInvoiceForCollection(Set<Id> invoiceIds).
- Guard against null/empty input; re-query only Invoices not yet registered
(
Gateway_Reference__c = null) with a positive Amount__c.
- For each qualifying Invoice, POST to
callout:Payment_Gateway/collections/register
and capture the returned referenceId.
- Update all successfully-registered Invoices with a single bulk DML call after the loop.
Best Practices
callout=true is required for the payment gateway callout.
- Filtering on
Gateway_Reference__c = null makes re-running this method safe —
an Invoice that's already registered is never re-submitted for collection.
- Filtering out non-positive amounts avoids submitting a nonsensical collection request to
the gateway.
- DML is batched into one call after all callouts complete.
Approach
- 1@future(callout=true) is required for the payment gateway callout.
- 2Filter Gateway_Reference__c = null AND Amount__c > 0 so only unregistered, meaningful invoices are submitted.
- 3Parse the response with JSON.deserializeUntyped and pull out referenceId to store as Gateway_Reference__c.
- 4Collect updates into one list and call update toUpdate; once after the loop, not per Invoice.
Hard
Async ApexFuture Methods
27. @future(callout=true): Sync New Employee to External HR System
Problem #537 · Salesforce Apex Coding Challenge
Business Scenario
New hires are first recorded in Salesforce via a custom Employee__c object, then
must be provisioned in the company's external HR/payroll system via a REST callout, storing the
returned HR-system employee id for future reference.
Requirements
- Write
ExternalHrSyncUtil with a @future(callout=true) method
syncNewEmployees(Set<Id> employeeIds).
- Guard against null/empty input; re-query only
Employee__c records not yet
synced (HR_System_Id__c = null).
- For each qualifying Employee, POST to
callout:External_HR_System/employees
with their name, email, department, and hire date, and capture the returned
employeeId as HR_System_Id__c.
- Update all successfully-synced Employees with a single bulk DML call after the loop.
Best Practices
callout=true is required for the HR system callout.
- Filtering on
HR_System_Id__c = null keeps this method idempotent — an
Employee already provisioned externally is never re-submitted.
- Formatting the
Date field explicitly (Hire_Date__c.format())
before serializing avoids relying on JSON's default Date representation for an external system.
- DML happens once, after all callouts in the loop complete.
Approach
- 1@future(callout=true) is required for the external HR system callout.
- 2Filter HR_System_Id__c = null so an already-provisioned Employee is never re-submitted.
- 3Format Hire_Date__c explicitly with .format() before including it in the JSON payload.
- 4Collect updates into one list and call update toUpdate; once after the loop, not per Employee.
Hard
Async ApexFuture Methods
28. @future(callout=true): Push Account Updates to Enterprise Data Warehouse
Problem #538 · Salesforce Apex Coding Challenge
Business Scenario
The analytics team maintains an enterprise data warehouse fed by an ETL pipeline that
accepts Account updates via a REST endpoint. Whenever key Account fields change, this method
pushes an incremental update batch to the warehouse.
Requirements
- Write
DataWarehouseSyncUtil with a @future(callout=true) method
pushAccountUpdates(Set<Id> accountIds).
- Guard against null/empty input; re-query the Accounts by Id; return early if none found.
- Build one combined JSON payload with key reporting fields (industry, revenue, employee
count, last modified) and send a single POST callout to
callout:Data_Warehouse/etl/accounts/upsert.
- Accept both
200 and 202 (accepted-for-async-processing) as
success status codes from the warehouse's ETL endpoint.
Best Practices
callout=true is required for the data warehouse callout.
- One bulk payload and one callout for the whole set of Accounts — an ETL endpoint is
built to accept batches, so sending one record at a time would be both slower and wasteful
of the callout limit.
- A longer
setTimeout (20s) reflects that warehouse ETL endpoints may take
longer to acknowledge a batch than a typical lightweight API.
- Wrap the callout in
try/catch(CalloutException).
Approach
- 1@future(callout=true) is required for the data warehouse ETL callout.
- 2Return early with accounts.isEmpty() if the query finds nothing to push.
- 3A warehouse ETL endpoint may accept the batch asynchronously — treat both 200 and 202 as success, not just 200.
- 4Use a longer setTimeout (e.g. 20000ms) to give the ETL endpoint more time to acknowledge a larger batch.
Expert
Async ApexFuture Methods
29. @future(callout=true): Fraud Detection Check on New Orders
Problem #539 · Salesforce Apex Coding Challenge
Business Scenario
Every new Order should be scored by a third-party fraud detection API. Orders that come back
with a high risk score must be automatically put On Hold and flagged for manual
review before fulfillment proceeds.
Requirements
- Write
FraudDetectionUtil with a @future(callout=true) method
checkOrdersForFraud(Set<Id> orderIds).
- Guard against null/empty input; re-query the Orders by Id; return early if none found.
- For each Order, POST its amount, customer, and billing country to
callout:Fraud_Detection_API/score and parse the returned riskScore.
- Store the score on
Fraud_Risk_Score__c for every scored Order.
- If the score is at or above
HIGH_RISK_SCORE_THRESHOLD (75), also set
Status = 'On Hold' and Fraud_Flag__c = true.
- Apply all updates with a single bulk DML call after the callout loop.
Best Practices
callout=true is required for the fraud detection API callout.
- Acting on the response (holding high-risk Orders) rather than just logging the score
makes this a real automated control, not just a passive audit trail.
- A named constant (
HIGH_RISK_SCORE_THRESHOLD) keeps the risk cutoff tunable
without touching the scoring logic.
- DML is batched into a single call after every Order in the set has been scored.
Approach
- 1@future(callout=true) is required for the fraud detection API callout.
- 2Traverse Account.Name and Account.BillingCountry directly in the SOQL query.
- 3Cast the parsed riskScore safely: Integer.valueOf(String.valueOf(result.get('riskScore'))).
- 4Only set Status = 'On Hold' and Fraud_Flag__c = true when riskScore >= HIGH_RISK_SCORE_THRESHOLD — otherwise just store the score.
Expert
Async ApexFuture Methods
30. @future(callout=true): Synchronize Subscription Status With Billing Platform
Problem #540 · Salesforce Apex Coding Challenge
Business Scenario
Subscriptions can be cancelled or paused directly in the external billing platform (e.g. by
a customer through a self-service portal), and Salesforce needs to periodically pull the
authoritative status back for each Subscription__c already linked to that platform.
Requirements
- Write
BillingPlatformSyncUtil with a @future(callout=true)
method syncSubscriptionStatus(Set<Id> subscriptionIds).
- Guard against null/empty input; re-query only Subscriptions already linked to the billing
platform (
Billing_Platform_Id__c != null); return early if none found.
- For each Subscription, GET its current status from
callout:Billing_Platform/subscriptions/{id}/status.
- Only queue an update when the remote status actually differs from the local
Status__c.
- Apply all updates with a single bulk DML call after the callout loop.
Best Practices
callout=true is required for the billing platform callout.
- Filtering to Subscriptions with a
Billing_Platform_Id__c avoids attempting a
callout for records that were never actually linked externally.
- Comparing the remote status to the current local value before queuing an update avoids
no-op DML when nothing has actually changed on the billing platform's side.
- DML is batched into one call after every Subscription in the set has been checked.
Approach
- 1@future(callout=true) is required for the billing platform callout.
- 2Filter Billing_Platform_Id__c != null so only already-linked Subscriptions are checked.
- 3Build the endpoint dynamically: 'callout:Billing_Platform/subscriptions/' + sub.Billing_Platform_Id__c + '/status'.
- 4Compare remoteStatus != sub.Status__c before adding to toUpdate — skip subscriptions with no real change.
Expert
Async ApexFuture Methods
31. @future(callout=true): Stream Order Transactions to Analytics Platform
Problem #541 · Salesforce Apex Coding Challenge
Business Scenario
The BI team's external analytics platform ingests business events via a batch events API.
Every time Orders are activated, this method streams one "order_activated" event per Order to
the platform for real-time dashboards.
Requirements
- Write
AnalyticsPlatformUtil with a @future(callout=true) method
streamOrderTransactions(Set<Id> orderIds).
- Guard against null/empty input; re-query only
Activated Orders; return early
if none found.
- Build one event object per Order (type, order number, amount, industry, effective date)
and send them all as a single batch payload to
callout:Analytics_Platform/events/batch.
- Accept both
200 and 202 as success status codes.
Best Practices
callout=true is required for the analytics platform callout.
- Sending one combined events array in a single POST, rather than one callout per Order,
keeps this well within the callout limit at any Order volume.
- Formatting
EffectiveDate explicitly with .format() keeps the
date representation predictable for the receiving analytics system.
- Wrap the callout in
try/catch(CalloutException).
Approach
- 1@future(callout=true) is required for the analytics platform callout.
- 2Filter Status = 'Activated' so only genuinely completed Orders generate analytics events.
- 3Wrap the events List in an outer Map ({ 'events': [...] }) before calling JSON.serialize on the whole payload.
- 4Treat both 200 and 202 as success — analytics ingestion endpoints often accept batches asynchronously.
Expert
Async ApexFuture Methods
32. @future: Generate Contract PDF Document Asynchronously
Problem #542 · Salesforce Apex Coding Challenge
Business Scenario
When a Contract is finalized, a document summarizing its terms should be generated and
attached to the record. Document generation is pure in-Salesforce work — building text/PDF
content and storing it as a ContentVersion — with no external system
involved, so it must run asynchronously to keep it off the record-save transaction,
but it does not need an HTTP callout.
Requirements
- Write
ContractDocumentGeneratorUtil with a plain @future method
generateContractDocuments(Set<Id> contractIds) — no
(callout=true), since nothing here calls out to an external system.
- Guard against null/empty input; re-query only Contracts not yet documented
(
Document_Generated__c = false); return early if none found.
- Build one
ContentVersion per Contract with a text body summarizing its
key terms, insert them in bulk, then link each to its Contract via
ContentDocumentLink.
- Mark each successfully-documented Contract
Document_Generated__c = true.
Best Practices
- Only add
(callout=true) to @future when the method genuinely
performs an HTTP callout — this method never does, so the plain annotation is correct and
avoids implying a network dependency that doesn't exist.
- All
ContentVersion records are built and inserted in one bulk DML call, then
the resulting Ids are re-queried once to build the links in bulk too — never per-record DML.
- Filtering on
Document_Generated__c = false keeps this method idempotent —
a Contract already documented is never regenerated.
Approach
- 1No callout is made here — use a plain @future, not @future(callout=true).
- 2Filter Document_Generated__c = false so an already-documented Contract is never regenerated.
- 3Blob.valueOf(body) turns a String into the Blob needed for ContentVersion.VersionData.
- 4After inserting ContentVersion records, re-query them for ContentDocumentId before creating ContentDocumentLink rows.
Master
Async ApexFuture Methods
33. @future(callout=true): Multi-System Order Fulfillment Integration
Problem #543 · Salesforce Apex Coding Challenge
Business Scenario
When an Order is confirmed, two external systems must be notified in
sequence: first the warehouse system (to begin picking/packing), and only if that succeeds, the
shipping carrier system (to schedule a pickup). Both notifications must happen off the main
transaction.
Requirements
- Write
OrderFulfillmentIntegrationUtil with a @future(callout=true)
method notifyWarehouseSystem(Set<Id> orderIds) that calls
callout:Warehouse_System/dispatch for all qualifying (Activated) Orders in one
bulk callout.
- If — and only if — the warehouse callout succeeds, chain to the carrier notification.
- Apex forbids one
@future method from calling another, so the
second integration step must be a separate Queueable class
(CarrierNotificationQueueable, implementing Queueable, Database.AllowsCallouts),
enqueued via System.enqueueJob from inside the future method.
CarrierNotificationQueueable.execute() re-queries the Orders by Id and sends
one bulk callout to callout:Carrier_System/pickup-request.
Best Practices
callout=true is required on the @future method, and
Database.AllowsCallouts is required on the Queueable — each async
context needs its own callout permission declared.
- The future-cannot-call-future restriction is a real Apex limitation; the standard
workaround is exactly this — enqueue a
Queueable (which itself is allowed to
start further async work) to perform the next step.
- Only notifying the carrier when the warehouse step actually succeeded avoids scheduling a
pickup for an order the warehouse never received.
- Both integration points still bulkify their callouts (one JSON payload, one HTTP call)
rather than looping a callout per Order.
Approach
- 1An @future method can never call another @future method directly — Apex throws a runtime error if you try.
- 2The standard workaround: from inside the @future method, call System.enqueueJob(new SomeQueueable(...)) to hand off the next async step.
- 3CarrierNotificationQueueable must implement Queueable, Database.AllowsCallouts to be allowed to perform an HTTP callout from execute().
- 4Only enqueue the carrier Queueable when the warehouse callout actually succeeded — check the status code before chaining.
About ApexArena
ApexArena is a free, browser-based Salesforce Apex coding practice platform covering every major topic tested on the Salesforce Platform Developer I (PD1) and Platform Developer II (PD2) certification exams. All problems run directly in your browser with instant pass/fail feedback, best-practice linting (SOQL in loops, DML in loops, empty catch blocks), and governor limit monitoring — no Salesforce Developer Edition org required.
Related tutorials:
Apex Triggers ·
SOQL ·
Batch Apex ·
Interview Q&A ·
Governor Limits