Mastering Apex Code in Salesforce Development

Salesforce Apex is a powerful, strongly-typed, object-oriented programming language designed for developers to create custom business logic on the Salesforce platform. Whether you’re managing large datasets, automating processes, or integrating with external applications, Apex provides the flexibility and control needed to build enterprise-grade applications. Mastering Apex Code in Salesforce Development is essential for any developer working on this platform.
In this blog post, we will explore some advanced Apex code snippets that can enhance your Salesforce development workflow.
Boost your skills with Salesforce online training in Admin, Developer, and AI modules. Gain hands-on experience through real-world projects to master Salesforce solutions and advance your career.
1. Sending Custom Email Alerts in Apex
Salesforce’s built-in email functionality can be extended with Apex to send custom email alerts based on business logic. Here’s an example of how you can send an email when a loan application is submitted.
Email Sending Code:
public class EmailUtility {
public static void sendCustomEmail(String toAddress, String ccAddress, String subject, String body) {
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
mail.setToAddresses(new String[] { toAddress });
if (!String.isEmpty(ccAddress)) {
mail.setCcAddresses(new String[] { ccAddress });
}
mail.setSubject(subject);
mail.setPlainTextBody(body);
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
}
Code Explanation:
The EmailUtility
class provides a method to send emails with an optional CC address. It initializes a Messaging.SingleEmailMessage
object and sets the recipient using setToAddresses
. If a CC address is provided, it adds it using setCcAddresses
. The email subject and body are assigned using setSubject
and setPlainTextBody
. Finally, the email is sent using Messaging.sendEmail
, ensuring proper delivery.
See also: Data types in Salesforce Apex
2. Preventing Recursive Triggers in Apex
A common challenge in Apex development is preventing a trigger from running recursively. Salesforce triggers can fire multiple times in a single transaction if not carefully managed. Here’s an example of how to prevent recursion:
Recursive Trigger Prevention Code:
trigger PreventRecursiveTrigger on Account (before update) {
Set<Id> processedAccountIds = new Set<Id>();
for (Account acc : Trigger.new) {
Account oldAcc = Trigger.oldMap.get(acc.Id);
if (oldAcc != null && acc.SomeField__c != oldAcc.SomeField__c) {
if (!processedAccountIds.contains(acc.Id)) {
processedAccountIds.add(acc.Id);
}
}
}
if (!processedAccountIds.isEmpty()) {
// Implement necessary logic here to avoid recursion
}
}
Code Explanation:
This Apex trigger runs before update on the Account
object to prevent recursive updates. It initializes a set of processed Account IDs to track changes. Within the loop, it checks if SomeField__c
has changed by comparing the new and old values using Trigger.oldMap
. If the field has changed and the account hasn’t been processed before, its ID is added to the set. Finally, if the set is not empty, additional logic can be executed to prevent unwanted recursion.
3. Efficiently Processing Large Data with Batch Apex
Handling large datasets can be a challenge in Salesforce, especially when working with millions of records. This is where Batch Apex comes into play, enabling you to process records asynchronously in chunks. Here’s an example of how you can create a simple batch class to update Account records in batches:
Batch Apex Code:
global class MyBatchClass implements Database.Batchable<SObject> {
global Database.QueryLocator start(Database.BatchableContext BC) {
return Database.getQueryLocator([SELECT Id, Name FROM Account WHERE CreatedDate = LAST_N_DAYS:30]);
}
global void execute(Database.BatchableContext BC, List<Account> scope) {
for (Account acc : scope) {
acc.Name = 'Updated - ' + acc.Name; // Prefixing instead of suffixing
}
update scope;
}
global void finish(Database.BatchableContext BC) {
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
mail.setToAddresses(new String[]{'admin@example.com'});
mail.setSubject('Batch Process Completed');
mail.setPlainTextBody('The batch process has completed successfully.');
Messaging.sendEmail(new Messaging.SingleEmailMessage[]{mail});
}
}
Code Explanation:
This Apex batch class processes Account
records created in the last 30 days using a SOQL query in the start
method. The execute
method loops through the retrieved records and prefixes the Name
field with 'Updated - '
, instead of appending text at the end. The modified records are then updated in bulk. In the finish
method, an email notification is sent to the administrator upon successful batch completion. This ensures proper tracking of batch processing without requiring manual intervention.
See also: Data types in Salesforce Apex
4. Automating Record Updates with Apex Triggers
Triggers in Salesforce allow you to execute custom business logic before or after data manipulation events (insert, update, delete). Let’s take an example where we automatically update related Contact records whenever an Account record is updated.
Trigger Code:
trigger AccountContactTrigger on Account (after update) {
List<Contact> contactsToUpdate = new List<Contact>();
for (Account acc : Trigger.new) {
if (acc.Industry == 'Finance' && acc.Rating == 'Hot') {
for (Contact con : [SELECT Id, LastName FROM Contact WHERE AccountId = :acc.Id]) {
con.LastName = 'VIP-' + con.LastName; // Prefixing with 'VIP'
contactsToUpdate.add(con);
}
}
}
if (!contactsToUpdate.isEmpty()) {
update contactsToUpdate;
}
}
Code Explanation:
This Apex trigger runs after an Account update and updates related Contact records based on specific conditions. Instead of checking if the Industry is 'Technology'
, it now checks if the Industry is 'Finance'
and the Rating is 'Hot'
. If both conditions are met, it queries related Contact
records and prefixes their LastName with 'VIP-'
to indicate priority customers. The modified contact records are stored in a list and updated in bulk, ensuring efficient processing. This prevents unnecessary DML operations while maintaining data consistency.
5. Building Custom REST APIs in Apex
With Apex, you can also create custom REST APIs that expose endpoints for external systems to interact with Salesforce. Here’s an example of a simple REST API that retrieves and updates loan details.
Custom REST API Code:
@RestResource(urlMapping='/LoanAPI/*')
global with sharing class LoanAPI {
@HttpGet
global static LoanResponse getLoanDetails(RestRequest req, RestResponse res) {
LoanResponse response = new LoanResponse();
response.status = 'Success';
response.message = 'Loan details retrieved successfully!';
response.timestamp = System.now();
return response;
}
@HttpPatch
global static String updateLoanDetails(String loanId, String newStatus) {
Loan__c loanRecord = [SELECT Id, Status__c FROM Loan__c WHERE Id = :loanId LIMIT 1];
loanRecord.Status__c = newStatus; // Updating loan status instead of amount
update loanRecord;
return 'Loan status updated successfully!';
}
public class LoanResponse {
public String status;
public String message;
public Datetime timestamp; // Added timestamp for response tracking
}
}
Code Explanation:
This Apex REST API class provides endpoints to retrieve and update loan records. The @HttpGet
method returns loan details with an additional timestamp
field to track when the response was generated. Instead of modifying the Loan Amount, the @HttpPatch
method updates the Loan Status, making it more adaptable for workflow automation. The loan record is queried using loanId
, and only the Status__c field is modified before updating the record. A structured response class is included with a timestamp to improve logging and API response tracking.
See also: Understanding REST API in Salesforce
6. Writing Unit Tests for Apex Code
Unit testing is crucial to ensure your Apex code works as expected and to avoid issues in production. Here’s an example of a simple unit test for updating an Account record:
Apex Unit Test Code:
@isTest
public class AccountTest {
@isTest
public static void testAccountIndustryUpdate() {
// Create test data
Account testAcc = new Account(Name = 'Sample Account', Industry = 'Finance');
insert testAcc;
// Simulate industry change
testAcc.Industry = 'Healthcare';
update testAcc;
// Query and verify changes
Account result = [SELECT Industry FROM Account WHERE Id = :testAcc.Id];
System.assertEquals('Healthcare', result.Industry);
}
}
Code Explanation:
This Apex test class is designed to verify updates to the Account
object’s Industry field. It creates a test account with the Industry set to 'Finance'
and inserts it into the database. The test then simulates a change to the Industry field by updating it to 'Healthcare'
. After updating the record, it queries the account and asserts that the Industry field has been correctly updated. This ensures that the logic works as intended and any updates to the Industry
field are properly reflected in the database.
7. Using Custom Settings for Configurable Logic
Custom Settings can store data that can be accessed across the organization. Let’s say you have configurable business logic that depends on a certain setting, and you want to retrieve the value of that setting to change behavior dynamically.
Custom Setting Code:
CustomSetting__c setting = CustomSetting__c.getInstance('MyCustomSetting');
if (setting != null && setting.Status__c == 'Enabled') {
// Logic for enabled status
System.debug('Custom Setting is Enabled. Executing related logic...');
// Perform necessary operations based on the setting
List<Account> accountsToUpdate = [SELECT Id, Name FROM Account WHERE Status__c = 'Pending'];
for (Account acc : accountsToUpdate) {
acc.Status__c = 'Processed';
}
update accountsToUpdate;
// Send confirmation email
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
mail.setToAddresses(new String[]{'admin@example.com'});
mail.setSubject('Custom Setting Triggered');
mail.setPlainTextBody('The custom setting has been checked, and the required updates were made.');
Messaging.sendEmail(new Messaging.SingleEmailMessage[]{mail});
}
Code Explanation:
In this Apex code, we retrieve a Custom Setting MyCustomSetting
using getInstance
. We check whether the Status__c
field of the setting is set to 'Enabled'
. If the setting is enabled, the code proceeds to query Account
records where Status__c
is 'Pending'
, updates their status to 'Processed'
, and performs a bulk update. Additionally, an email notification is sent to an admin with details about the changes. This code demonstrates how to leverage custom settings for configurable logic, handle multiple records, and send automated notifications based on certain conditions.
See also: Custom Page Layouts
8. Using Custom Metadata Types for Dynamic Configuration
Custom Metadata Types allow you to store and manage configuration data, and they are deployable. For example, you might use custom metadata to manage different configurations for different business processes.
Custom Metadata Example Code:
List<CustomMetadata__mdt> metadata = [SELECT MasterLabel, DeveloperName, Value__c FROM CustomMetadata__mdt WHERE DeveloperName = 'ConfigData'];
if (!metadata.isEmpty()) {
for (CustomMetadata__mdt record : metadata) {
System.debug('Master Label: ' + record.MasterLabel);
System.debug('Developer Name: ' + record.DeveloperName);
System.debug('Custom Value: ' + record.Value__c);
}
} else {
System.debug('No metadata records found for DeveloperName: ConfigData');
}
Code Explanation:
In this Apex code, we query CustomMetadata__mdt
for records where the DeveloperName equals 'ConfigData'
and retrieve multiple fields: MasterLabel, DeveloperName, and Value__c. If the query returns any records, we loop through each metadata record and print the values of these fields using System.debug
. If no records are found, a debug message is logged indicating that no matching metadata was found. This snippet demonstrates how to query and work with Custom Metadata Types to store and retrieve configurable values in Salesforce.
9. Creating Complex Custom Validation Logic
You can implement complex validation rules in Apex that go beyond what is possible with standard validation rules. This could include cross-object validation or validation based on custom criteria.
Validation Code:
public class CustomValidation {
public static void validateAccount(Account acc) {
if (acc.AnnualRevenue < 1000000 && acc.Industry == 'Technology') {
// Add custom validation error if conditions are met
acc.addError('Technology companies must have an annual revenue over 1 million.');
}
// Additional check for 'Finance' industry
if (acc.AnnualRevenue < 5000000 && acc.Industry == 'Finance') {
acc.addError('Finance companies must have annual revenue over 5 million.');
}
// Check if the account name is too short
if (acc.Name != null && acc.Name.length() < 5) {
acc.addError('Account name must be at least 5 characters long.');
}
}
}
Code Explanation:
In this Apex validation class, the validateAccount
method is used to validate Account records before they are saved. The first validation checks if an account in the Technology industry has an AnnualRevenue lower than 1 million and adds an error if the condition is met. The second check is for accounts in the Finance industry, requiring an AnnualRevenue over 5 million. Additionally, the method ensures that the Account Name is at least 5 characters long. These validations enforce business rules by adding errors to the record when conditions are not met, preventing invalid data from being saved.
See also: Database methods in Salesforce Apex
10. Handling Asynchronous Jobs with Future Methods
Apex allows you to run long-running operations asynchronously using future methods. This is useful when you want to process something in the background without affecting the user experience. Here’s an example of using a future method to process data.
Future Method Code:
@future
public static void updateAccountName(List<Id> accountIds) {
List<Account> accounts = [SELECT Id, Name, Industry FROM Account WHERE Id IN :accountIds];
// Loop through each account and update the name
for (Account acc : accounts) {
if (acc.Industry == 'Technology') {
acc.Name = 'Tech: ' + acc.Name;
} else {
acc.Name = 'General: ' + acc.Name;
}
}
// Perform the update operation
try {
update accounts;
} catch (DmlException e) {
System.debug('Error updating accounts: ' + e.getMessage());
}
}
Code Explanation:
This Apex method is marked with @future
, indicating it will run asynchronously. It takes a list of Account Ids, queries those accounts along with their Name and Industry fields, and updates the Account Name based on the Industry. If the account is in the Technology industry, the name is prefixed with “Tech: “, otherwise, it’s prefixed with “General: “. After modifying the names, the accounts are updated in the database. A try-catch block is added to handle potential DML exceptions, logging any errors that occur during the update process, ensuring better error handling and debugging.
Boost your career with Salesforce training in India.
Advance your career with Salesforce training in India and unlock new opportunities. Our expert-led program covers Admin, Developer, and AI modules, blending theoretical knowledge with practical projects to ensure a solid understanding of Salesforce concepts. Through real-world case studies and industry-focused assignments, you’ll build technical expertise and problem-solving skills.
Stand out in the competitive job market with personalized mentorship, interview coaching, and certification prep. With practical exercises and comprehensive study materials, you’ll be equipped to solve complex business challenges using Salesforce solutions.
Don’t wait—join a free demo session today and take the first step toward a successful Salesforce career in India!
Related links:
Classes – Salesforce Apex
SOSL Banking & Finance Examples
Data types in Salesforce apex
Understanding Apex Classes and Objects in Simple Terms
Salesforce Apex Code Best Practices
Salesforce apex programming examples
SOQL in Apex with code example