Future Apex

Future Apex (@future) Practice Problems

33 free Salesforce Apex challenges · Full problem statements, best-practice notes, and step-by-step hints · Solve live on ApexArena

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.

On this page
  1. 1. Future Method HTTP Callout
  2. 2. Queueable Chain
  3. 3. Platform Event Publisher
  4. 4. Schedulable Apex — Stale Case Cleanup
  5. 5. Queueable Apex with Chained Callout
  6. 6. @future: Async Account Rating Updater
  7. 7. Schedulable: Weekly Lead Digest
  8. 8. Queueable: Contact Industry Tagger
  9. 9. Batch Apex: Close Stale Opportunities
  10. 10. Batch Apex: Archive Stale Open Leads
  11. 11. Queueable: Update Account Tier Based on Revenue
  12. 12. @future: Asynchronous Lead Score Calculator
  13. 13. @future: Async Contact Mailing Address Sync from Account
  14. 14. @future(callout=true): Async Lead Enrichment via REST Callout
  15. 15. @future: Bulk Lead Score Reset After Campaign End
  16. 16. @future(callout=true): Account Credit Check via External API
  17. 17. @future: Send Welcome Email After Lead Creation
  18. 18. @future(callout=true): Push New Account to External CRM
  19. 19. @future(callout=true): Send SMS Alert on High-Priority Case Creation
  20. 20. @future(callout=true): Verify Account Billing Address
  21. 21. @future(callout=true): Notify Shipping System on Order Confirmation
  22. 22. @future(callout=true): Create Invoice in External Finance System
  23. 23. @future: Send Customer Satisfaction Survey After Case Closure
  24. 24. @future(callout=true): Push New Contacts to Marketing Automation Platform
  25. 25. @future(callout=true): Synchronize Product Catalog With External Catalog System
  26. 26. @future(callout=true): Notify Payment Gateway After Invoice Generation
  27. 27. @future(callout=true): Sync New Employee to External HR System
  28. 28. @future(callout=true): Push Account Updates to Enterprise Data Warehouse
  29. 29. @future(callout=true): Fraud Detection Check on New Orders
  30. 30. @future(callout=true): Synchronize Subscription Status With Billing Platform
  31. 31. @future(callout=true): Stream Order Transactions to Analytics Platform
  32. 32. @future: Generate Contract PDF Document Asynchronously
  33. 33. @future(callout=true): Multi-System Order Fulfillment Integration
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:

  1. Accepts a List<Id> of Contact IDs in its constructor
  2. In execute(QueueableContext ctx), queries those Contacts along with their Account's Industry
  3. Sets Contact.Description to "Industry: " + account.Industry
  4. 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).

Halfway there — solve them live

Every problem above runs in a real in-browser Apex editor with instant pass/fail feedback and best-practice linting. No Salesforce org needed.

Create Free Account →
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> from all queried Accounts, then JSON.serialize(...) it as a single request body.
  • 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.

Practice All 33 Problems Free

Write and run real Apex code right in your browser — instant pass/fail feedback, best-practice linting, and governor limit monitoring. No Salesforce org needed.

Create Free Account → Explore All Problems
More Practice Topics
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