Future Apex

Future Apex (@future) Practice Problems

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

This guide walks through 16 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
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 ApexTriggers

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).

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 →
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
  • 1WHERE IsClosed = false AND CloseDate < TODAY filters open stale opps.
  • 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: SELECT Id, AnnualRevenue FROM Account WHERE Id IN :accountIds.
  • 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: SELECT Id, LeadSource, Industry FROM Lead WHERE Id IN :leadIds.
  • 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: [SELECT Id, LeadId FROM CampaignMember WHERE CampaignId IN :campaignIds AND LeadId != null]
  • 2Step 2: collect into Set leadIds; if (leadIds.isEmpty()) return;
  • 3Step 3: [SELECT Id, Lead_Score__c FROM Lead WHERE Id IN :leadIds]
  • 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.

Practice All 16 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