Salesforce Apex Code Best Practices

In this post, we will talk about Salesforce Apex’s best practices. Apex code is used to write custom and robust business logic. As with any language, there are key coding principles and best practices that will help you write efficient, scalable code. This post will discuss Apex Code’s best practices for writing code on the Salesforce platform. We will also learn how to resolve Apex limitations with Apex Code Best Practices. Here are Salesforce Developer Best Practices.

Why we need Salesforce Apex Best Practices

Best practice recommended by Salesforce because Apex runs in a multitenant environment; the Apex runtime engine strictly enforces several Salesforce Governor limits to ensure that runways Apex code does not monopolize shared resources.

Regarding Salesforce Apex’s best practices, there are a few key things to remember. Let’s talk about some examples of Salesforce apex code best practices.

1. Bulkify Apex Code

The first principle is to write code for multiple records at a time. We should write scalable code and avoid hitting the governor. Let’s understand with an example. We use a hard-coded index in the code below [0]. This means the code will only work on a single record.

Trigger AccountTrigger on Account(before insert){
  Account acc= Trigger. New[0];
  if(acc.Name != null){
    // DO someything
  }
}

Solution

In the above Trigger, the code explicitly accesses only the first record in the trigger.new collection by using the syntax Trigger.New[0]. Instead, the trigger should properly handle the entire collection of Accounts using Trigger.new collection.

Trigger AccountTrigger on Account(before insert){
  for(Account acc: trigger.New){
    if(acc.Name != null){
      // DO someything
    }
  }
}

2. Avoid SOQL & DML inside for Loop

Do not place SOQL or DML(insert/update/delete/undelete) statements inside a loop. When these operations are placed inside a for loop, database operations are invoked once per iteration of the loop, making it very easy to reach these SFDC governor limits.

For(Account acc: Trigger.new){
 for(Contact con:[select id from contact where accountId = :acc.Id]){
 }
}

Solution

  1. Move SOQL/DML out of loops.
  2. Query: If you need query results, get all the records using a single query and iterate over the resultset.
  3. Update: If you need to update, batch up the data into a collection and invoke DML once for that collection.

Here is an example of putting SOQL Query outside of for loop.

Map<Id, Account> accountMap=new Map<id, Account>([select id,name, (select id from contacts) from account where id in:trigger.newmap.keyset()]);

for(Account acc: accountMap.values()){
	For(Contact con:acc.Contacts){ 
	}
}

3. Querying Large Data Sets

In Salesforce, we have a governance limit that SOQL queries can return 50,000 records. If you are working on large data sets that exceed the heap size limit, the SOQL query for loop must be used. Here is an example of a SOQL query for loop as in one of the following examples.

Solution: Use a SOQL query for loop.

for( List<Account> accList: [select id, name from Account where BillingCountry  LIKE ‘%United%’]) {
	// add your logic here.
}

Check this post to learn about SOQL Best Practices. Here are the best practices

  1. Building Efficient & Selective Queries.
  2. Avoid Common Causes of Non-Selective SOQL Queries.
  3. Use Query Optimizer.
  4. Avoiding querying on formula fields.
  5. Custom Indexes Containing null Rows.
  6. Avoid SOQL injection.
  7. Avoid using negative filter operators.

Optimize SOQL Queries to avoid Timeout Issues.

  1. While Querying unnecessary records and fields, the transaction will take additional time to retrieve the result from the SOQL query. This might lead to Timeout issues.
  2. Sometimes, these small mistakes lead to issues that can take much time and effort to debug and fix. 
  3. As an Apex coding best practice,  In SOQL queries, avoid querying columns that are not used in the code – e.g., In a process, if the need is only the LastName of the contact, query only the last name, there is no need to query all the fields from the contact record.
  4. Similarly, query the required number of records using right filters and joins so unwanted records are not retrieved.

4. Use of Map of Sobject

In a few cases, we need to get the value of records from different sobject based on looped sobject records. In that case, we can use Map of Sobjects to get the values and avoid SOQL queries in for loop.

Solution

  1. Use Apex Collections to query data and store the data in memory efficiently.
  2. A combination of using collections and streamlining SOQL queries can substantially help write efficient Apex code and avoid governor limits
Map<Id, Account> accountMap=new Map<Id, Account> ([select id, name from Account where name like ‘%Inc.%’]);

for(Contact con: [select id,AccountId, name from Contact where AccountId in:accountMap.keyset()]{
	//Get Related Account data using Contact field
	Account acc=accountMap.get(con.AccountId);
}

5. Use of the Limits Apex Methods

Use Apex Limits Methods to Avoid Hitting SF Governor Limits. Many of us face governor limit errors in trigger/classes/test classes. A few of the governor limit errors as follows:-

  1. Too many SOQL queries: 101.
  2. Dml rows 10001.
  3. too many query rows 50001.

Here is the snippet to how to use Apex limit Apex methods.

System.debug('Total Number of SOQL allowed in this Apex code: ' +  Limits.getLimitQueries());

Account acc=[select id, name from Account where BillingCity!=null  order by createdDate desc limit 1];

System.debug('1. Number of Queries used in this Apex code so far: ' + Limits.getQueries());

Now, using the above Limit methods we can check how many SOQL queries we can issue in the current Apex Context

If(Limits.getLimitQueries() - Limits.getQueries()>0) {
  // Execute SOQL Query here.
}
  1. System class Limits – use this to output debug message about each governor limit – 2 version
  2. First: (e.g. Limits.getHeapSize()) returns the amount of the resource used in the current context.
  3. Second: Contains the word limit (e.g. Limits.getLimitDmlStatements()) and returns the total amount of the available resource for that context.
  4. Use in the Apex code directly to throw error messages before reaching a governor limit.
  5. When an end-user invokes Apex code that surpasses more than 50% of any governor limit, you can specify a user in your organization to receive an email notification of the event with additional details.

6. Avoid Hardcoding IDs

Don’t hard code ID in Apex Code. Deploying apex code between sandbox and production may fail because id may not be the same, for putting recordType Id.

Id accountRTId=’xxxxxxxxxx’;

This will ensure that code can be deployed safely to different environments. Try to get the record Type Id Using Schema methods, get the Record Type Id of the sobject.

Id accountRTId= Schema.sobjectType.Account.getRecordTypeInfosByName().get(‘RTName’).getRecordTypeId();

Use of custom settings/custom metadata to avoid hard coding IDs. For example, at times you need to handle login credentials in your code. To avoid this, it is an Apex coding best practice to create custom settings or custom metadata where one can store the credentials and configuration values and retrieve those values dynamically inside the apex.

7. Use Database Methods while doing DML operation

An Apex transaction represents a set of operations executed as a single unit. All DML operations in a transaction are completed successfully, or if an error occurs in one operation, the entire transaction is rolled back, and no data is committed to the database. Apex allows you to generate a savepoint, a point in the request that specifies the state of the database at that time.

Using the Database class method, you can specify whether or not to allow for partial record processing if errors are encountered.

Database.SaveResult[] accountResults=Database.Insert(accountsToBeInsert, false);
// Using above code, we can work on failure records
  For(Database.SaveResult sr:accountResults) {
	If(!sr.isSuccess()) {
	// add your logic here 
	for(Database.Error err : sr.getErrors()) {
	}
  }
}

8. Exception Handling in Apex Code

DML statements return run-time exceptions if something goes wrong in the database during the execution of the DML operations. Don’t forget to use Try catch blocks for exception handling. With Apex, you can write code that responds to specific exceptions.

try{
	// Apex Code 
}Catch(Exception e){
}

It’s also essential to use proper exception handling to ensure that your code can recover gracefully from errors and to avoid hard-coded values that can make your code less flexible and more challenging to maintain.

Learn about Different types of Exceptions in Salesforce.

9. Write One Trigger per Object per event

Apex triggers within Salesforce are designed to help you automate certain tasks. Apex triggers allow you to perform custom actions before and after events in Salesforce. While writing Apex Trigger, you should follow the best practice and create one Trigger per object.

A single Apex Trigger is all you need for one particular object. If you develop multiple Triggers for a single object, you have no way of controlling the order of execution if those Triggers can run in the same contexts.

Learn how to thoroughly and cleanly control your Apex code with Trigger Frameworks, why they’re useful, and how to implement them. Check this post to learn about Trigger Framework.

10. Use Asynchronous Apex

Apex, written within an asynchronous method, gets its own independent set of higher governor limits. For example, the number of SOQL queries doubles from 100 to 200 when using asynchronous calls. The total heap size and maximum CPU time are larger for asynchronous calls.

Apex Best Practices. Demystifying Asynchronous Processing

11. Security and Sharing in Apex Code

One of the Apex Code best practices is developers must understand to code secure applications on the Salesforce Platform. Like how to enforce data security and prevent SOQL injection attacks in Apex.

Enforcing Object & FLS Permissions in Apex

Apex doesn’t enforce object-level and field-level permissions by default. Let see how we can enforce the CRUD & FLS in Apex.

  1. Schema methods
  2. WITH SECURITY_ENFORCED
  3. Security.stripInaccessible()
  4. Database operations in user mode (pilot)

Sharing Clauses for Apex Classes

Apex generally runs in system context, which means the current user’s permissions and field-level security take place during code execution. Our Apex code should not expose sensitive data to Users that is hidden via security and sharing settings. Hence, Apex security and enforcing the sharing rule are most important. Let’s see how we can implement the sharing in Apex

  1. with sharing
  2. without sharing
  3. inherited sharing

Check this post to learn more about Security in Apex.

12. Make reusability of Apex Code

The best code is written away from the keyboard. Break methods into multiple smaller methods if possible, making your code more modular and easier to read and maintain. A critical best practice with Apex Code is always to write scalable and reusable code so that it can be reused in another functionality or module. This entails making code generic so it can be reused with minimum modification.

Learn more about Apex Enterprise Patterns here.

13. Code coverage

Unit tests are the tests performed by the developers to ensure that functionality is working as expected, considering Both positive and negative tests. We should not focus on the percentage of code coverage. But it should follow the test class best practices.

  1. Bulk Records
  2. Positive & Negative testing.
  3. Restricted User testing.
  4. One Assert Statement per method.
  5. should not use @isTest(seeAllData=true).
Apex Code coverage best practices

14. Return Early Pattern

Return early is the way of writing functions or methods so that the expected positive result is returned at the end of the function, and the rest of the code terminates the execution.

static void someAction(Case record)
    {
        if (/*condition 1*/){
            // do stuff
            return;
        }
        if (/*condition2*/)
        {
            // do stuff
            return;
        }
        // do stuff
        return;
    }

15. Avoid nesting loops within loops

Nested loops should be avoided in Apex controllers because they may slow down the processing of the page or may hit the governing limits for the page. One easy way, which gives some nice structure to the code, is to make the inner loop a separate function or minimize using loops altogether.

Often, we encounter code where we have two or three or more nested Loops, which affect performance. A simple way of avoiding nested loops is using Maps. For example, you can find or create a key for each item of the second loop and put the key and the value into the Map.

16. Don’t mix Apex, Process Builders, Workflow Rules, and Record-Triggered flows

Every object should have an automation strategy based on the needs of the business and the Salesforce team supporting it. In general, you should choose one automation tool per object. One of the many inevitable scenarios of older orgs is to have Apex triggers mixed in with Auto launched flows/processes or, more recently, Process Builders mixed in with Record-Triggered flows. This can lead to a variety of issues:

  1. Poor performance.
  2. Unexpected results due to the inability to control the order of operations in the ‘stack’.
  3. Increase in technical debt with admins/devs not collaborating.
  4. Documentation debt.

One common approach is to separate DML activity by Apex and let declarative tools handle non-DML activities like email and in-app alerts — just be careful and ensure none of your Apex conflicts.

17. Apex Naming Conventions

Another critical best practice surrounding Apex Code is following a proper naming convention. The naming convention in Salesforce is a rule to follow as you decide what to name your identifiers like class, variable, constant, method, etc. But it is not forced to follow. So, it is known as convention, not rule. Naming conventions make the application easier to read and maintain. Check this post to learn more.

First and foremost, it’s essential to follow a consistent coding style. This will make it easier for other developers to read and understand your code, and can help prevent errors and bugs.

18. Setup Code review checklist and Code Review process

40%–80% of the lifetime cost of a piece of software goes to maintenance. To avoid future tech debt, we should follow the code review process in each project and should create one code review checklist for developers.

19. Apex Performance

How to measure the performance of your code and understand where time is being used within a transaction, as well as the actions to take and some best practices to keep things running nicely. Some Tips are here.

  1. Query only required fields in SOQL.
  2. Retrieve only relevant data rows and apply filters and limits.
  3. Remove debug statements from the code once testing is done.

Check tips for Apex performance troubleshooting.

Salesforce Apex Best Practices Video

Check out our below session to learn about Apex’s best practices in 2022.

YouTube video

FAQ’s

What are the Salesforce Apex Code Best Practices in 2023?

1. Bulkify Apex Code
2. Avoid SOQL & DML inside for Loop
3. Querying Large Data Sets
4. Use of Map of Sobject
5. Use of the Limits Apex Methods
6. Avoid Hardcoding IDs
7. Use Database Methods while doing DML operation
8. Exception Handling in Apex Code
9. Write One Trigger per Object per event
10. Use Asynchronous Apex
11. Security and Sharing in Apex Code
12. Make reusability of Apex Code
13. Code coverage
14. Return Early Pattern
15. Avoid nesting loops within loops
16. Don’t mix Apex, Process Builders, Workflow Rules, and Record-Triggered flows
17. Naming Conventions.
18. Setup Code review checklist and Code Review process

How do I practice Apex coding in Salesforce?

Create one Free Salesforce Developer org. Start writing code first, then make it work. After that, follow Apex Best practices to make your code clean. Then, over time, you will learn about all best practices and why it needed.

What is the fastest way to improve Apex?

It’s essential that once you have your application up and running, you also ensure it is performant, but what does performance mean? Fast? Scalable? Within Governor Limits? How about all of these and then some? Check this session to learn more.

Summary

This article covers many of the core Salesforce Apex code best practices. We discussed how to bulkify your code by handling all incoming records instead of just one. We also illustrated how to avoid having SOQL queries inside a loop to avoid governor limits. By following these principles, you are on a great path for success with Apex code.

Finally, take advantage of the many tools and resources available to Apex developers, including the Salesforce Developer Console, the Apex Unit Testing framework, and online communities like the Salesforce Stack Exchange. Check our Ultimate Salesforce Best Practices Guide to learn more.

Amit Chaudhary
Amit Chaudhary

Amit Chaudhary is Salesforce Application & System Architect and working on Salesforce Platform since 2010. He is Salesforce MVP since 2017 and have 17 Salesforce Certificates.

He is a active blogger and founder of Apex Hours.

Articles: 460

5 Comments

  1. About the last image in the code coverage section Test.startTest and Test.stopTest explanation is a kind of misleading for novice

  2. Point #16. Completely agree, however can you share an authoritative Salesforce.com reference documenting this? Thanks.

Leave a Reply

Your email address will not be published. Required fields are marked *