This guide walks through 47 general-purpose Apex classes — collections, string handling, business logic, and service-layer design. 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.
Create a utility class TemperatureConverter with two static methods:
celsiusToFahrenheit(Decimal c) → returns DecimalfahrenheitToCelsius(Decimal f) → returns DecimalFormulas: F = C × 9/5 + 32 | C = (F − 32) × 5/9
Create a class OpportunityHelper with a static method
isWon(String stage) that returns true if the stage equals
"Closed Won" (case-insensitive), otherwise false.
Also add isLost(String stage) that returns true for "Closed Lost".
Create a class StringUtils with these static methods:
isPalindrome(String s) — returns true if s reads the same forwards and backwards (ignore case)countWords(String s) — returns the number of words (split on whitespace)capitalizeFirst(String s) — capitalizes the first letter of every wordCreate a class MathHelper with these static methods:
factorial(Integer n) — returns n! (use recursion or iteration)isPrime(Integer n) — returns true if n is primefibonacci(Integer n) — returns the nth Fibonacci number (0-indexed)Create a class AccountService with:
getHotAccounts() — returns a List<Account> where Rating = 'Hot', ordered by NamedeactivateSmallAccounts(Decimal maxRevenue) — sets Active__c = false on all Accounts with AnnualRevenue < maxRevenue, updates them, returns the count updatedCreate a class DiscountCalculator with a static method
getDiscountedPrice(Decimal originalPrice, Integer quantity):
Returns the final price per unit after discount, rounded to 2 decimal places.
Create a class RecordTypeHelper with a static method:
getId(String objectName, String developerName)
Returns the Id of the RecordType matching the given sObject name and
developer name, or null if not found.
Must cache results in a static Map so subsequent calls for the same object don't trigger more SOQL.
Create a class JsonParser with a static method
parseContacts(String jsonStr) that parses a JSON array of objects like:
[{"name":"Alice","email":"alice@x.com"},{"name":"Bob","email":"bob@x.com"}]
Returns a List<Contact> with LastName set from name
and Email set from email.
Create a class HttpCalloutService with a method
callWithRetry(String endpoint, Integer maxRetries).
It should make an HTTP GET request, and if the response status is not 200,
retry up to maxRetries times with a 1-second delay between attempts.
Return the response body on success, or throw a CalloutException after all retries are exhausted.
Create a Batch Apex class DeactivateOldContactsBatch that:
Database.Batchable<SObject>LastActivityDate < 365 days agoActive__c = false on each Contact in the execute methodCreate a class AccountValidator with a custom exception
InvalidAccountException (extends Exception).
Add a static method validate(Account acc) that throws
InvalidAccountException if:
Returns true if valid.
Create a Queueable Apex class WelcomeEmailQueueable that:
QueueableList<Id> of Contact IDs in its constructorexecute(): queries the contacts, sends a welcome email to eachSystem.enqueueJob()Implement a selector class ContactSelector following enterprise patterns:
getByIds(Set<Id> ids) — returns Contacts by ID with standard fieldsgetByAccountId(Id accountId) — returns Contacts for an AccountgetByEmail(String email) — returns a Contact matching the emailAll queries must select: Id, FirstName, LastName, Email, Phone, AccountId
Write an Apex class LeadScorer with a static method calculateScore
that computes a numeric lead score based on attributes.
Scoring rules:
Maximum possible score: 100
static Integer calculateScore(String industry, Decimal annualRevenue, Integer employees, String phone, String email)
Write an Apex class StageValidator with a static method isValidTransition
that enforces allowed stage progressions for Opportunities.
Allowed transitions:
static Boolean isValidTransition(String fromStage, String toStage)
Write an Apex class TerritoryMapper with a static method getRegion
that maps US state codes to sales regions.
Region mappings:
'Unknown'static String getRegion(String stateCode)
'Unknown'Create an Apex REST service that exposes Account data via HTTP endpoints.
Requirements:
@RestResource(urlMapping='/accounts/*')GET method annotated with @HttpGet that queries Accounts
and returns them as a JSON-serializable listPOST method annotated with @HttpPost that accepts an
Account name in the request body and inserts a new AccountDELETE method annotated with @HttpDelete that deletes
an Account by ID from the URL parameterRestContext.request and RestContext.response where neededglobal staticBuild a robust HTTP callout handler class and its corresponding mock for testing.
Requirements for HttpCalloutHandler:
fetchExternalData(String endpoint) that makes an HTTP GET calloutApiResponse with fields:
Integer statusCode, String body, Boolean successRequirements for HttpCalloutHandlerMock:
HttpCalloutMock interfacerespond method returning a mock 200 responseHttp, HttpRequest, HttpResponse classesDesign a robust custom exception hierarchy for a payment processing service.
Requirements:
PaymentException extending ExceptionInsufficientFundsException — includes Decimal balance and Decimal required fieldsInvalidCardException — includes String cardType fieldPaymentGatewayException — includes Integer errorCode and String gatewayMessagePaymentProcessor class with method processPayment(Decimal amount, String cardType, Decimal accountBalance)InvalidCardException if cardType is not in ('Visa','Mastercard','Amex')InsufficientFundsException if accountBalance < amounttrueWrite an Apex class StringUtils with a static method
reverseWords(String sentence) that reverses the order of words
in a sentence while preserving each word's characters.
"Hello World Apex" → "Apex World Hello""I love Salesforce" → "Salesforce love I"null or blank → ""public static String reverseWords(String sentence)
Write an Apex class WordCounter with a static method
countWords(List<String> words) that returns a
Map<String, Integer> counting the frequency of each word
(case-insensitive).
['apex','java','apex','APEX'] → {apex:3, java:1}public static Map<String, Integer> countWords(List<String> words)
Write an Apex class MathUtils with a static method
factorial(Integer n) that returns n!.
0! = 1 and 1! = 15! = 120-1 for negative inputspublic static Integer factorial(Integer n)
Write an Apex class FizzBuzz with a static method
generate(Integer n) that returns a List<String>
of 1 through n with:
"Fizz""Buzz""FizzBuzz"public static List<String> generate(Integer n)
Your sales operations team needs to automatically classify accounts into revenue tiers to determine service level agreements and pricing models.
Write an Apex class AccountTierService with a static method
getRevenueTier(Decimal annualRevenue) that returns a tier label based on the account's annual revenue.
| Annual Revenue | Tier |
|---|---|
| < $50,000 or null | 'Bronze' |
| $50,000 – $249,999 | 'Silver' |
| $250,000 – $999,999 | 'Gold' |
| ≥ $1,000,000 | 'Platinum' |
public static String getRevenueTier(Decimal annualRevenue)
null input gracefully — null revenue maps to 'Bronze'.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 →Your finance team requires that discount approvals are routed to the correct authority level based on the percentage discount being offered on an opportunity.
Write an Apex class DiscountApprovalService with a static method
getApprovalLevel(Decimal discountPercent) that returns the required approver.
| Discount % | Approval Level |
|---|---|
| null or < 0 | 'Invalid' |
| 0% – 5% | 'Rep' |
| >5% – 15% | 'Manager' |
| >15% – 25% | 'Director' |
| >25% | 'VP' |
public static String getApprovalLevel(Decimal discountPercent)
Your account management team needs a single numeric health score (0–100) per account to prioritise outreach. The score is calculated from five weighted factors.
Write an Apex class AccountHealthService with a static method
getHealthScore(String industry, Decimal annualRevenue, String rating,
Integer contactCount, String billingState) that returns an Integer score.
public static Integer getHealthScore(String industry, Decimal annualRevenue, String rating, Integer contactCount, String billingState)
Write an Apex class LeadQualifier with a static method
qualify(String source, Integer numEmployees, Decimal budget)
that returns a letter grade based on company size and budget.
with sharing on the classWrite an Apex class InvoiceCalculator with a static method
calculateTotal(List<Decimal> lineItems, Decimal taxRate, Decimal discountPct)
that computes the final invoice total.
subtotal = sum of all positive line items afterDiscount = subtotal × (1 − discountPct ÷ 100) total = afterDiscount × (1 + taxRate ÷ 100) Return total rounded to 2 decimal places.
.setScale(2)Write an Apex class PhoneFormatter with a static method
formatPhone(String rawPhone) that formats any 10-digit US phone
number into the standard (XXX) XXX-XXXX pattern.
(XXX) XXX-XXXX"""415-555-1234" → "(415) 555-1234""4155551234" → "(415) 555-1234""415.555.1234" → "(415) 555-1234""12345" → "12345" (not 10 digits)Write an Apex class SLAChecker with a static method
isBreached(String priority, Integer hoursOpen) that returns
true when a case has exceeded its SLA time limit.
| Priority | Max Hours |
|---|---|
| Critical | 2 |
| High | 4 |
| Medium | 8 |
| Low | 24 |
false for null/blank priority or null hoursOpenfalse for unknown priority valuesMap constant — no if-else chainsWrite an Apex class NameFormatter with two static methods:
public static String fullName(String salutation, String firstName, String lastName)
Concatenates non-blank parts separated by a single space.
e.g. fullName("Mr.", "John", "Smith") → "Mr. John Smith"
e.g. fullName(null, "John", "Smith") → "John Smith"
public static String lastFirst(String firstName, String lastName)
Returns "Last, First" format.
e.g. lastFirst("John", "Smith") → "Smith, John"
e.g. lastFirst(null, "Smith") → "Smith"
String.isBlank() for null-safe blank checksWrite a service class ContactMergeService with a static method
mergeContactsByAccount(Id accountId) that merges duplicate Contacts
under the same Account.
Two Contacts are duplicates when they share the same full name (case-insensitive).
The oldest Contact (earliest CreatedDate) is kept as the master record.
public with sharing class.IllegalArgumentException when accountId is null.CreatedDate ASC; group by full name (case-insensitive).Database.merge(master, List<Id>) with up to 2 IDs per call.System.debug the merged count — visible in the Output tab.Write AccountHealthScoreService with two static methods:
calculateHealthScore(Id accountId) — single-account wrapper that System.debugs the score.calculateHealthScores(Set<Id> accountIds) — bulk-safe: exactly 4 SOQL queries total.Marketing wants every customer's Account to accumulate loyalty points
whenever one of their Opportunities closes as Closed Won. Points are earned
at a rate of 1 point per $100 of the won Amount, stored on a custom field
Loyalty_Points__c (Number) on Account.
awardPoints(List<Opportunity> closedWonOpps)
that a trigger can call with the Opportunities that just became Closed Won.Set first, then
query and update once outside any loop.WITH SECURITY_ENFORCED and a LIMIT on the query respect the
running user's field/object-level security and cap the result size.try/catch(DmlException) so a partial failure never
throws an unhandled exception back to the caller.Sales wants a single, reusable place to compute the discount percentage a customer
qualifies for, based on their Account.Type (standard picklist values such as
Technology Partner and Reseller count as "partner" accounts) and
AnnualRevenue.
Technology Partner or Reseller) with
revenue ≥ 1,000,000 get 20%; ≥ 250,000 get 15%; otherwise 10%.AnnualRevenue must be treated as 0, never throw an error.Operations wants Orders blocked before they're created if the requested quantity
exceeds available inventory. Orders live on a custom object Order__c with a
lookup Product__c to Product2 and a Quantity__c
number field. Product2 has a custom Stock_Quantity__c field
tracking units on hand.
validateStock(List<Order__c> ordersToInsert) that throws a
custom exception when any product's total requested quantity across the batch exceeds
its available stock.Exception subclass communicates the specific failure reason
clearly to the caller (e.g. a trigger) instead of a generic error.WITH SECURITY_ENFORCED and a LIMIT protect the query and
respect field-level security on Stock_Quantity__c.Fulfillment needs a reusable utility to compute shipping charges consistently across the
org. Orders live on the custom object Order__c, which carries
Ship_Region__c (Text), Ship_Country__c (Text), and
Weight_Kg__c (Number).
calculateShippingCharge(region, country, weightKg) returns a base rate
(domestic USA vs. international) plus a per-kilogram charge, plus a surcharge for
designated remote regions (Alaska, Hawaii, Remote Islands).calculateShippingChargesForOrders(List<Order__c>)
that returns a Map<Id, Decimal> of Order Id to computed charge.Management wants a dashboard-ready breakdown of Closed Won Opportunities by calendar month for a given date range — total count and total dollar amount per month, sorted chronologically.
getMonthlySummaries(Date startDate, Date endDate) returning a
list of MonthSummary inner-class instances (month label, won count, total
amount), sorted oldest month first.StageName = 'Closed Won' and
CloseDate falls within the given range (inclusive).Map, avoiding any per-month query.WITH SECURITY_ENFORCED and a LIMIT keep the query safe and
bounded for a reporting-style read.YYYY-MM) guarantees a stable,
chronological order regardless of the order records come back in.Support's SLA policy grants agents a number of business days (Monday through Friday) to resolve a Case, starting from when it was opened. Weekends must never count toward the SLA clock, or agents effectively lose time they were promised.
calculateDueDate(Datetime startDateTime, Integer businessDays)
that advances one calendar day at a time, only counting weekdays, until
businessDays weekdays have been added.startDateTime unchanged rather than throwing.Datetime.format('u') returns the ISO day-of-week ('1' Monday through '7'
Sunday) without any manual modulo-arithmetic date math, which is easy to get off-by-one.isWeekend helper keeps the
day-counting loop readable and makes the rule easy to unit test in isolation.Finance needs a single Apex service that computes tax for an order's taxable amount, but the calculation rules differ per country: some use a flat percentage, others use progressive tiers based on the amount. New countries are added periodically, and the rules should not require rewriting a giant if/else chain each time.
TaxStrategy interface with one method,
calculateTax(Decimal taxableAmount).FlatRateTaxStrategy (a single percentage) and
TieredTaxStrategy (rate depends on which threshold the amount clears).calculateTax(String countryCode, Decimal taxableAmount)
that looks up the right strategy for the country and delegates to it, falling back to a
default strategy for unrecognized countries.setScale(2) for currency-correct output.Revenue operations wants product pricing to automatically flex based on the purchasing
Account's customer segment (Enterprise, Mid-Market, or
SMB, tracked on a custom Account field Customer_Segment__c) and a
seasonal peak surcharge that applies during November and December.
calculatePrice(Decimal listPrice, String customerSegment, Date effectiveDate).effectiveDate falls in November or December.listPrice must be treated as 0 rather than throwing a null pointer
exception.calculatePrice focused on the overall pricing sequence (discount, then
surcharge) rather than a long nested conditional.HR wants a configurable bonus calculation for the custom object
Employee__c (fields Salary__c, Performance_Rating__c
1-5, and Years_Of_Service__c). Bonuses combine a performance component and a
capped loyalty component, but only employees who meet a minimum performance bar qualify at
all.
Map<Id, Decimal> for a list of employees.Multiple objects (Account billing address, Contact mailing address, a future Order shipping address) all need the same address-quality checks before being saved. Build one reusable service so the validation rules live in exactly one place.
validateAddress(street, city, state, postalCode, country) returns a
ValidationResult inner class holding isValid and a list of
human-readable errors.USA/US/blank), the state must be a valid 2-letter code and the
postal code must match US ZIP format (12345 or 12345-6789).validateAccounts(List<Account>) returning a
Map<Id, ValidationResult> for the standard Account billing address
fields.validateAddress reusable across Account, Contact, and any future object —
the bulk wrapper adapts a specific SObject's fields to that generic signature.Customer Success wants renewal quotes computed automatically for the custom object
Subscription__c (Base_Amount__c, Consecutive_Renewals__c,
Auto_Renew__c checkbox, Contract_Term_Months__c). Multiple
discounts can stack: loyalty (more consecutive renewals = bigger discount, capped),
auto-renew enrollment, and committing to an annual-or-longer term.
Auto_Renew__c is true.Contract_Term_Months__c >= 12.Map<Id, Decimal> wrapper.Math.min() enforces the loyalty discount cap cleanly instead of a
manual if-check, and keeps the cap logic in one expression.totalDiscountPct and applied once at
the end — avoids compounding rounding errors from applying each discount sequentially to
an already-discounted amount.Sales wants a "customers who bought this also bought that" recommendation feature for an Account's product page: given an Account, suggest up to 5 Products that similar customers purchased but this Account has not, ranked by how often those similar customers bought each candidate product.
getRecommendations(Id accountId) returning up to 5
Product2 records.OpportunityLineItems).WITH SECURITY_ENFORCED and a LIMIT on every query keeps
each stage safe and bounded regardless of how large the customer base grows.Finance wants a Closed Won revenue report broken down by fiscal quarter, where the
fiscal year does not necessarily start in January — the org's fiscal year start month is a
configurable input (e.g. 2 means the fiscal year runs February through
January).
getQuarterlyReport(Integer year, Integer fiscalYearStartMonth)
returning exactly four QuarterSummary entries (Q1-Q4) — total revenue and
deal count of Closed Won Opportunities closing in that fiscal quarter.fiscalYearStartMonth,
then bucket each Opportunity's CloseDate into one of the four 3-month
quarters relative to that start.fiscalYearStartMonth to January (1) when not supplied.WITH SECURITY_ENFORCED and a LIMIT keep this
reporting-style query safe and bounded.summaryByQuarter with all four quarters before the loop
guarantees the report always returns Q1-Q4 even for quarters with zero deals.Multiple triggers across the org (Case escalations, Opportunity stage changes, Contract renewals) all need to notify a user, but some notifications should be emails and others should be follow-up Tasks. Build one generic notification service so trigger code never has to know the delivery mechanism.
NotificationChannel interface with
send(String recipientId, String subject, String body).EmailChannel (using Messaging.SingleEmailMessage
targeted at a User/Contact Id) and TaskChannel (creating a Task
owned by the recipient).notify(NotificationChannel channel, String recipientId, String subject, String body)
as the generic entry point, plus a bulk notifyMultiple(..., List<String> recipientIds, ...)
for sending the same message to many recipients.NotificationChannel interface decouples "who is calling" from "how
the message is delivered" — a new channel (e.g. Chatter post, SMS) can be added without
touching any existing trigger.try/catch (EmailException for email,
DmlException for the Task insert) so a delivery failure never bubbles up as
an unhandled exception to the caller.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 ProblemsApexArena 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