Apex is the core programming language for Salesforce, and mastering it is essential for Salesforce developers. To gain confidence in Apex, nothing works better than hands-on experience. That’s why I’ve compiled a list of 100 practice programs to help you learn and master Apex programming. These exercises range from basic to advanced, covering everything from simple SOQL queries to advanced triggers and batch processing.
Whether you’re just starting your Salesforce journey or looking to sharpen your skills, these programs will give you a solid understanding of Apex concepts.
Section 1: Basics of Apex (1–20 Programs)
In this section, you will cover the essentials of Apex, like printing to the debug log, basic arithmetic, simple loops, and creating records. Here are some programs you will work on:
- Hello World – Start your journey by printing “Hello World” to the Salesforce debug log.
- Addition of Two Numbers – Learn simple arithmetic operations with integers.
- Even or Odd Check – Write a program to check if a number is even or odd.
- String Concatenation – Combine strings to form full names or custom messages.
- Creating Records in Salesforce – Learn to insert an Account record and automate object creation.
These foundational programs will help you understand how Apex integrates with the Salesforce platform while making use of basic programming constructs.
Section 2: Intermediate Apex Concepts (21–50 Programs)
After you’re comfortable with the basics, you can move to more complex concepts. In this section, you’ll work with SOQL (Salesforce Object Query Language) to query and manipulate Salesforce records, create triggers, and manage database operations.
Key programs in this section include:
- SOQL Queries with Filters – Learn how to filter records based on conditions.
- SOQL with Parent-Child Relationships – Query related records using parent-child relationships.
- Apex Triggers – Automate processes with triggers such as updating related objects when changes occur in records.
- Batch Apex – Process large data sets asynchronously using batch Apex classes.
By the end of this section, you will have a deep understanding of database operations and how to implement business logic with triggers.
Section 3: Advanced Apex Concepts (51–80 Programs)
The advanced programs take things to the next level. You will learn how to handle complex data processing using batch Apex, schedule jobs, and integrate with external systems using REST API.
Some highlights of this section include:
- Writing a Batch Apex Class – Learn how to process large numbers of records efficiently.
- Creating a Scheduler in Apex – Schedule recurring jobs to run your Apex code at specified times.
- Apex REST API – Create and consume custom REST APIs to interact with external systems.
- Custom Metadata and Settings – Store reusable configurations in Salesforce using custom metadata and settings.
These advanced concepts will prepare you for real-world projects where complex data handling and integrations are required.
Section 4: Expert-Level Programs (81–100 Programs)
In the final section, you’ll work on programs that involve error handling, future methods for asynchronous processing, and even building Visualforce page controllers. You’ll also learn to write test classes to ensure your code is robust and scalable.
Here’s a preview of what you’ll cover:
- Creating Custom Exceptions – Handle errors gracefully by writing custom exceptions.
- Future Methods – Learn how to perform long-running processes asynchronously.
- Visualforce Page Controllers – Dive into building custom Visualforce pages by writing controllers.
- Fibonacci Series – Implement the Fibonacci series using loops in Apex.
This section will give you the confidence to handle large projects, ensuring your code is optimized and scalable.
Conclusion: Mastering Apex Programming
This set of 100 practice programs is designed to guide you from a beginner to an expert in Apex programming. By practicing these programs, you’ll develop a deep understanding of Salesforce’s capabilities, how to interact with records programmatically, and how to automate business logic using triggers, batch processes, and more.
Don’t rush through the exercises—take your time to understand each program and adapt them to your own Salesforce environment. Happy coding!
- Print Hello World to Debug Log:
System.debug(‘Hello World!’); - Addition of Two Numbers:
Integer a = 10;
Integer b = 20;
System.debug(‘Sum: ‘ + (a + b)); - Check if a Number is Even or Odd:
Integer num = 5;
if(num % 2 == 0) {
System.debug(‘Even Number’);
} else {
System.debug(‘Odd Number’);
} - String Concatenation:
String firstName = ‘John’;
String lastName = ‘Doe’;
System.debug(‘Full Name: ‘ + firstName + ‘ ‘ + lastName); - Simple For Loop:
for(Integer i = 1; i <= 5; i++) {
System.debug(‘Number: ‘ + i);
} - Simple While Loop:
Integer i = 1;
while(i <= 5) {
System.debug(‘Number: ‘ + i);
i++;
} - Create an Account:
Account acc = new Account(Name = ‘Test Account’);
insert acc; - Create Multiple Accounts:
List accounts = new List();
for(Integer i = 1; i <= 3; i++) {
accounts.add(new Account(Name = ‘Account ‘ + i));
}
insert accounts; - Update Account Name:
Account acc = [SELECT Id, Name FROM Account WHERE Name = ‘Test Account’ LIMIT 1];
acc.Name = ‘Updated Test Account’;
update acc; - Delete an Account:
Account acc = [SELECT Id, Name FROM Account WHERE Name = ‘Updated Test Account’ LIMIT 1];
delete acc; - Query Accounts with SOQL:
List accounts = [SELECT Id, Name FROM Account LIMIT 5];
for(Account acc : accounts) {
System.debug(‘Account Name: ‘ + acc.Name);
} - Query Accounts with Filter:
List accounts = [SELECT Id, Name FROM Account WHERE Industry = ‘Technology’];
for(Account acc : accounts) {
System.debug(‘Tech Account: ‘ + acc.Name);
} - Insert Contact Related to Account:
Account acc = [SELECT Id, Name FROM Account LIMIT 1];
Contact con = new Contact(FirstName = ‘John’, LastName = ‘Doe’, AccountId = acc.Id);
insert con; - Update Contact’s Last Name:
Contact con = [SELECT Id, LastName FROM Contact WHERE LastName = ‘Doe’ LIMIT 1];
con.LastName = ‘Smith’;
update con; - Delete a Contact:
Contact con = [SELECT Id FROM Contact WHERE LastName = ‘Smith’ LIMIT 1];
delete con; - Simple IF Statement:
Integer x = 10;
if(x > 5) {
System.debug(‘x is greater than 5’);
} - Nested IF-ELSE Statements:
Integer x = 5;
if(x > 10) {
System.debug(‘x is greater than 10’);
} else if(x == 5) {
System.debug(‘x is 5’);
} else {
System.debug(‘x is less than 5’);
} - Loop Through List of Integers:
List numbers = new List{1, 2, 3, 4, 5};
for(Integer num : numbers) {
System.debug(‘Number: ‘ + num);
} - SOQL Query with ORDER BY:
List accounts = [SELECT Id, Name FROM Account ORDER BY Name ASC LIMIT 5];
for(Account acc : accounts) {
System.debug(‘Account Name: ‘ + acc.Name);
} - Query Contacts with Related Account Name:
List contacts = [SELECT Id, LastName, Account.Name FROM Contact LIMIT 5];
for(Contact con : contacts) {
System.debug(‘Contact: ‘ + con.LastName + ‘, Account Name: ‘ + con.Account.Name);
}
Intermediate Programs (21–40)
- Trigger to Update Account Description Before Insert:
trigger UpdateAccountDescription on Account (before insert) {
for(Account acc : Trigger.new) {
acc.Description = ‘New account created!’;
}
} - Trigger to Update Contact Last Name Before Update:
trigger UpdateContactLastName on Contact (before update) {
for(Contact con : Trigger.new) {
con.LastName = ‘Updated Last Name’;
}
} - Trigger to Prevent Account Deletion:
trigger PreventAccountDeletion on Account (before delete) {
for(Account acc : Trigger.old) {
if(acc.Name == ‘Cannot Delete’) {
acc.addError(‘This account cannot be deleted.’);
}
}
} - Batch Apex to Update Accounts:
global class BatchUpdateAccounts implements Database.Batchable {
global Database.QueryLocator start(Database.BatchableContext BC) {
return Database.getQueryLocator(‘SELECT Id, Name FROM Account WHERE Industry = ‘Technology”);
} global void execute(Database.BatchableContext BC, List scope) {
for(Account acc : (List)scope) {
acc.Industry = ‘Updated Technology’;
}
update scope;
} global void finish(Database.BatchableContext BC) {
System.debug(‘Batch Update Completed’);
}
} - Scheduler to Run Batch Class:
public class AccountBatchScheduler implements Schedulable {
public void execute(SchedulableContext sc) {
BatchUpdateAccounts batch = new BatchUpdateAccounts();
Database.executeBatch(batch, 200);
}
} - Create a Custom Object:
CustomObject__c obj = new CustomObject__c(Name = ‘Test Custom Object’);
insert obj; - Create Multiple Custom Object Records:
List objs = new List();
for(Integer i = 1; i <= 5; i++) {
objs.add(new CustomObject__c(Name = ‘Custom Object ‘ + i));
}
insert objs; - SOQL Query on Custom Object:
List objs = [SELECT Id, Name FROM CustomObject__c LIMIT 5];
for(CustomObject__c obj : objs) {
System.debug(‘Custom Object: ‘ + obj.Name);
} - Update Custom Object Field:
CustomObject__c obj = [SELECT Id, Name FROM CustomObject__c WHERE Name = ‘Custom Object 1’ LIMIT 1];
obj.Name = ‘Updated Custom Object 1’;
update obj; - Delete Custom Object Record:
CustomObject__c obj = [SELECT Id FROM CustomObject__c WHERE Name = ‘Updated Custom Object 1’ LIMIT 1];
delete obj;
Intermediate Programs Continued (31-50)
- SOQL Query with WHERE Clause on Custom Object:
List objs = [SELECT Id, Name FROM CustomObject__c WHERE Name LIKE ‘Custom%’ LIMIT 5];
for(CustomObject__c obj : objs) {
System.debug(‘Filtered Custom Object: ‘ + obj.Name);
} - Apex Method to Get Account by Name:
public class AccountService {
public static Account getAccountByName(String accountName) {
return [SELECT Id, Name FROM Account WHERE Name = :accountName LIMIT 1];
}
} - Trigger to Count Opportunities on Account:
trigger CountOpportunities on Opportunity (after insert, after delete) {
Set accountIds = new Set();
for(Opportunity opp : Trigger.new) {
accountIds.add(opp.AccountId);
}
List accountsToUpdate = [SELECT Id, (SELECT Id FROM Opportunities) FROM Account WHERE Id IN :accountIds];
for(Account acc : accountsToUpdate) {
acc.Number_of_Opportunities__c = acc.Opportunities.size();
}
update accountsToUpdate;
} - SOQL Query with Aggregate COUNT():
AggregateResult[] groupedResults = [SELECT Industry, COUNT(Id) FROM Account GROUP BY Industry];
for(AggregateResult ar : groupedResults) {
System.debug(‘Industry: ‘ + ar.get(‘Industry’) + ‘ – Number of Accounts: ‘ + ar.get(‘expr0’));
} - Query Opportunities and Update Stage:
List opps = [SELECT Id, StageName FROM Opportunity WHERE StageName = ‘Prospecting’];
for(Opportunity opp : opps) {
opp.StageName = ‘Qualification’;
}
update opps; - Query Accounts and Set Industry to ‘Technology’:
List accounts = [SELECT Id, Industry FROM Account WHERE Industry = NULL];
for(Account acc : accounts) {
acc.Industry = ‘Technology’;
}
update accounts; - Simple Apex REST Service:
@RestResource(urlMapping=’/AccountService/*’)
global with sharing class AccountService {
@HttpGet
global static Account getAccountById() {
RestRequest req = RestContext.request;
String accountId = req.requestURI.substring(req.requestURI.lastIndexOf(‘/’)+1);
return [SELECT Id, Name FROM Account WHERE Id = :accountId];
}
} - Using Future Method for Async Processing:
public class FutureExample {
@future
public static void updateAccountIndustry(Set accountIds) {
List accounts = [SELECT Id, Industry FROM Account WHERE Id IN :accountIds];
for(Account acc : accounts) {
acc.Industry = ‘Updated Industry’;
}
update accounts;
}
} - Apex Scheduler to Run Future Method:
public class SchedulerExample implements Schedulable {
public void execute(SchedulableContext sc) {
Set accountIds = new Set{‘001XXXXXXXXXXXX’};
FutureExample.updateAccountIndustry(accountIds);
}
} - Query with OFFSET to Implement Pagination:
List accounts = [SELECT Id, Name FROM Account ORDER BY Name ASC LIMIT 10 OFFSET 10];
for(Account acc : accounts) {
System.debug(‘Account Name: ‘ + acc.Name);
}
Advanced Programs (41-60)
- Batch Apex to Update Opportunities:
global class BatchUpdateOpportunities implements Database.Batchable {
global Database.QueryLocator start(Database.BatchableContext BC) {
return Database.getQueryLocator(‘SELECT Id, StageName FROM Opportunity WHERE StageName = ‘Prospecting”);
}
global void execute(Database.BatchableContext BC, List scope) {
for(Opportunity opp : (List)scope) {
opp.StageName = ‘Closed Won’;
}
update scope;
}
global void finish(Database.BatchableContext BC) {
System.debug(‘Opportunities Updated’);
}
} - Create a Custom Permission Set:
PermissionSet ps = new PermissionSet(Name=’Custom_Permission_Set’);
insert ps; - Custom Exception Class in Apex:
public class CustomException extends Exception {} - Custom Metadata Example:
public class CustomMetadataExample {
public static String getMetadataValue(String metadataName) {
CustomMetadata__mdt customData = [SELECT Value__c FROM CustomMetadata__mdt WHERE DeveloperName = :metadataName LIMIT 1];
return customData.Value__c;
}
} - Using System.runAs() for Test Class:
@isTest
public class TestExample {
static testMethod void testRunAs() {
User standardUser = [SELECT Id FROM User WHERE Profile.Name = ‘Standard User’ LIMIT 1];
System.runAs(standardUser) {
// Test logic here
}
}
} - Query Contacts Related to Specific Accounts:
List contacts = [SELECT Id, LastName, Account.Name FROM Contact WHERE AccountId IN (SELECT Id FROM Account WHERE Industry = ‘Technology’)]; - Apex Trigger to Automatically Create Task:
trigger CreateTask on Account (after insert) {
for(Account acc : Trigger.new) {
Task t = new Task(Subject = ‘Follow Up’, WhatId = acc.Id, Priority = ‘High’);
insert t;
}
} - Query Record and Display Record Type Name:
List accounts = [SELECT Id, Name, RecordType.Name FROM Account LIMIT 5];
for(Account acc : accounts) {
System.debug(‘Account Name: ‘ + acc.Name + ‘, Record Type: ‘ + acc.RecordType.Name);
} - Bulk Update Custom Object Records:
List customObjs = [SELECT Id, Name FROM CustomObject__c WHERE Name LIKE ‘Test%’];
for(CustomObject__c obj : customObjs) {
obj.Name = ‘Updated ‘ + obj.Name;
}
update customObjs; - Apex REST Service for Updating Contact:
@RestResource(urlMapping=’/UpdateContact/*’)
global with sharing class UpdateContactService {
@HttpPost
global static String updateContact(String contactId, String lastName) {
Contact con = [SELECT Id, LastName FROM Contact WHERE Id = :contactId LIMIT 1];
con.LastName = lastName;
update con;
return ‘Contact Updated’;
}
}name', 'Test Account'); RestResponse res = new RestResponse(); RestContext.request = req; RestContext.response = res; AccountAPI.createAccount('Test Account');
}
} - Use @future Annotation to Handle Long-Running Processes:
public class FutureMethodExample {
@future
public static void updateAccounts(Set accountIds) {
List accounts = [SELECT Id, Name FROM Account WHERE Id IN :accountIds];
for(Account acc : accounts) {
acc.Name = ‘Updated ‘ + acc.Name;
}
update accounts;
}
} - Test Class for Future Method:
@isTest
public class TestFutureMethodExample {
static testMethod void testFutureMethod() {
Test.startTest();
FutureMethodExample.updateAccounts(new Set{‘001XXXXXXXXXXXX’});
Test.stopTest();
}
} - Use Custom Labels in Apex:
public class CustomLabelExample {
public static String getLabel() {
return System.Label.MyCustomLabel;
}
} - Dynamic SOQL Example:
public class DynamicSOQLExample {
public static List getRecords(String query) {
return Database.query(query);
}
} - Create a Public Group Programmatically:
public class CreatePublicGroup {
public static void createGroup() {
Group g = new Group();
g.Name = ‘New Public Group’;
g.Type = ‘Regular’;
insert g;
}
} - Trigger to Prevent Insert of Duplicate Contact:
trigger PreventDuplicateContact on Contact (before insert) {
Set emailSet = new Set();
for(Contact con : Trigger.new) {
if(emailSet.contains(con.Email)) {
con.addError(‘Duplicate contact email found.’);
}
emailSet.add(con.Email);
}
} - Apex Email Template Example:
public class EmailTemplateExample {
public static void sendEmail(String toAddress) {
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
mail.setToAddresses(new String[] { toAddress });
mail.setTemplateId([SELECT Id FROM EmailTemplate WHERE DeveloperName = ‘MyTemplate’ LIMIT 1].Id);
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
} - Create a Map of Account and List of Contacts:
Map> accountContactMap = new Map>();
for(Account acc : [SELECT Id, (SELECT Id, LastName FROM Contacts) FROM Account LIMIT 10]) {
accountContactMap.put(acc.Id, acc.Contacts);
} - Use AggregateResult to Calculate Sum of Opportunities:
AggregateResult[] results = [SELECT SUM(Amount) FROM Opportunity];
System.debug(‘Total Opportunity Amount: ‘ + results[0].get(‘expr0’)); - Write an @isTest Class with @testSetup Method:
@isTest
public class TestSetupExample {
@testSetup
static void setupData() {
Account acc = new Account(Name = ‘Test Account’);
insert acc;
} static testMethod void testAccountSetup() {
Account acc = [SELECT Id, Name FROM Account WHERE Name = ‘Test Account’ LIMIT 1];
System.assertEquals(‘Test Account’, acc.Name);
}
} - Use Math Methods in Apex:
Decimal result = Math.sqrt(144);
System.debug(‘Square root of 144: ‘ + result); - Handle Batchable Error in Apex:
global class BatchErrorHandler implements Database.RaisesPlatformEvents {
global void execute(Database.BatchableContext BC, List scope) {
try {
// Batch logic here
} catch (Exception e) {
Database.PlatformEvent pe = new Database.PlatformEvent();
pe.ErrorMessage = e.getMessage();
insert pe;
}
}
} - Write a Batch Class to Process Large Data:
global class ProcessLargeDataBatch implements Database.Batchable {
global Database.QueryLocator start(Database.BatchableContext BC) {
return Database.getQueryLocator(‘SELECT Id FROM CustomObject__c’);
} global void execute(Database.BatchableContext BC, List scope) {
for(CustomObject__c obj : (List)scope) {
obj.Processed__c = true;
}
update scope;
} global void finish(Database.BatchableContext BC) {
System.debug(‘Processing Completed’);
}
} - Create a Trigger to Update Case Status:
trigger UpdateCaseStatus on Case (before insert, before update) {
for(Case c : Trigger.new) {
if(c.Status == ‘New’) {
c.Status = ‘In Progress’;
}
}
} - Use String Methods in Apex:
String name = ‘Salesforce’;
System.debug(‘Uppercase: ‘ + name.toUpperCase()); - Write a Test Class for Batch Apex:
@isTest
public class TestBatchApex {
static testMethod void testBatch() {
ProcessLargeDataBatch batch = new ProcessLargeDataBatch();
Database.executeBatch(batch, 100);
}
} - Trigger to Automatically Create a Task When Opportunity is Closed:
trigger CreateTaskOnOpportunityClose on Opportunity (after update) {
for(Opportunity opp : Trigger.new) {
if(opp.StageName == ‘Closed Won’ && opp.StageName != Trigger.oldMap.get(opp.Id).StageName) {
Task t = new Task(Subject = ‘Follow up on closed opportunity’, WhatId = opp.Id);
insert t;
}
}
} - Apex Class to Clone a Record:
public class CloneRecordExample {
public static SObject cloneRecord(SObject record) {
return record.clone(false, true);
}
} - Use ApexPages to Create a Custom Visualforce Page Controller:
public class CustomVFController {
public String accountId { get; set; } public Account getAccount() {
return [SELECT Id, Name FROM Account WHERE Id = :accountId];
}
} - Apex Class to Calculate the Fibonacci Series:
public class FibonacciSeries {
public static List generateSeries(Integer n) {
List series = new List{0, 1};
for(Integer i = 2; i < n; i++) {
series.add(series[i-1] + series[i-2]);
}
return series;
}
}