Skip to content
A modern office setting with a focus on a developer's desk. The desk features a large monitor displaying lines of code written in a syntax similar to Java, indicating it is Salesforce Apex.

Introduction to Salesforce Apex: Best Practices & Practical Tips

Apex is a strongly typed and object-oriented programming language. It was specifically developed for Salesforce, drawing inspiration from Java. It features a Java-like syntax but operates similarly to database stored procedures. This design allows developers to inject custom business logic into a variety of system events such as clicks on buttons, updates to related records, and interactions with Visualforce pages. Moreover, Apex code can be triggered by web service requests as well as by object triggers.

Why Apex?

Apex sits at the core of Salesforce’s development and operations (DevOps) ecosystem, primarily because it integrates deeply with the platform’s cloud infrastructure, facilitating a seamless workflow for tracking changes, managing deployments, and maintaining different versions through version control systems like GIT. The use of version control systems like GIT with Salesforce’s CLI and metadata API allows teams to automate the build, test, and deployment processes. This automation ensures that Apex code changes are consistently integrated, tested, and deployed across various environments, reducing manual errors and increasing efficiency.

It doesn’t matter what you are coding with Apex. You can create a trigger, handler, or simple Apex class. But, no matter what, you have to follow Apex best practices. Understanding best practices for Apex in Salesforce is crucial for developers aiming to build scalable, maintainable, and efficient applications. These practices encompass coding standards, error handling, performance optimization, and effective use of Apex’s object-oriented programming model.

Moreover, the preference for coding in Apex over using Salesforce’s low-code solutions, like Flows, stems from the complexity and limitations associated with low-code tools in complex Salesforce implementations. While Flows and other declarative tools offer quick wins for simple automation tasks, they can lead to a more convoluted solution architecture as the complexity of business processes increases. In contrast, Apex provides more granular control over custom logic and integrations, enabling developers to create more streamlined and efficient solutions.

Coding in Apex allows for a more disciplined approach to development within Salesforce, adhering to principles of modularity, reusability, and maintainability. It supports sophisticated error handling, transaction control, and performance optimization strategies that are often required in complex enterprise applications. This not only makes the development process more manageable but also simplifies the deployment and maintenance phases of the application lifecycle.

Best Practices in Apex Development

Adhering to established best practices in Apex development is essential for creating high-quality Salesforce applications. This includes:

  • Coding Standards: Adopting a consistent coding style and following Salesforce’s recommended guidelines to enhance readability and maintainability.
  • Error Handling: Implementing comprehensive error handling to ensure applications behave gracefully under all circumstances.
  • Performance Optimization: Utilizing bulkification, efficient SOQL queries, and asynchronous processing to optimize performance and scalability.

Let’s dive deeper into the Best Apex Practices, namely working with loops, avoiding hard coding, bulkification, reusability, testing, and naming conventions.

Working with Loops

You must avoid creating DML or SOQL queries in loops. When using DML (Data Manipulation Language) statements, a more efficient approach is to move these statements outside of loops. Instead, as we go through the loop, we should collect the records we intend to modify or update in a list. After the loop, we can then apply the DML operation to this list all at once. This method is generally safer and more effective for nearly all cases.

Moving SOQL queries out of loops can be a bit more complex and really depends on the specific situation. Consider the case where you’re trying to calculate the number of contacts associated with an account each time a new contact is added. In this scenario, you would loop through all the contacts, collect each one’s Account Id, and add those Ids to a set. Then, you can execute a single SOQL query outside of the loop with “AccountId IN :accountIds” to retrieve the related accounts, storing these in a map. After that, a final loop through the contacts allows you to access the associated Account information directly from the map. This process optimizes the operation by reducing the number of queries needed.

Why is it better to avoid hard coding, especially IDs

Using hard coded IDs and variables may seem to function properly and without any issues during development. However, the moment we transition our code into a production environment, those IDs may not correspond to the intended records anymore. This issue is particularly prevalent with Record Types that are developed in a sandbox environment and then transferred to production. Conversely, when a sandbox is created from a production environment, the hardcoded IDs will fail to direct to the accurate records, showcasing the limitations and risks of relying on hardcoded IDs in dynamic environments.

Instead of hardcoding an ID that corresponds to a particular record, a better approach is to save this ID in custom metadata and then fetch it when needed at runtime. Or, retrieve the ID at time of use with a separate SOQL invocation.  This method gives you the flexibility to update the ID as we move between different environments or whenever our requirements evolve.


Most of us are familiar with running into limits within the Salesforce platform. Here is one way to get around that. Running records in bulk, or bulkification refers to optimizing your code to efficiently process multiple records simultaneously. This is particularly important for triggers, which can process up to 200 records in a single operation, such as during a data import. If your code isn’t designed to handle these volumes, it may result in errors or, even more concerning, unpredictable outcomes. With bulkification we can work on record sets, perform bulkification on SOQL queries, also, as DML calls.

One Trigger Per Object

When you have multiple triggers set up on a single object, the sequence in which these triggers activate during a record save is unpredictable. It essentially seems random. Often, specific actions within a trigger are meant to follow a certain sequence, or an action might depend on the completion of another action beforehand. For example, setting up a parent lookup that needs to be referenced in subsequent actions.

This unpredictability in trigger execution introduces an element of randomness into our code, complicating both debugging and development processes. With this randomness, replicating specific scenarios accurately becomes challenging, making it hard to predict and address issues effectively.

Make your Code Reusable

Imagine creating a solution that you can use in various places, so you have to make it universal, such that it will suit everywhere you want to implement it. For example, you used the same piece of code in different classes, and unfortunately, there were bugs. If you create a reusable component or module, you won’t have to fix this piece of code everywhere you’ve used it—only in one place.

Test Positive and Negative Scenarios

Use your unit tests for more than just code coverage. When crafting tests, the focus should shift from merely aiming for code coverage to thoroughly testing various use cases of our code. The goal is to ensure that we’re testing the actual scenarios in which the code operates. To achieve this, we write multiple test methods. Some of these tests might target the same functions without contributing to an increase in covered lines of code. However, each test method is designed to execute our code in distinct scenarios, offering a comprehensive examination of its functionality. 

So, don’t only pass the tests, make sure that they are covering multiple cases. 

Naming Conventions

Implementing a naming convention in Salesforce is a strategic practice that brings structure, clarity, and efficiency to development and administrative processes. It’s an investment in the long-term health and scalability of the Salesforce environment. 

Practical Tips for Using SF CLI in Apex Development

Incorporating the Salesforce Command Line Interface (SF CLI) into your Apex development routine can significantly streamline and enhance the process. Here are detailed strategies to effectively use the SF CLI for a more efficient development workflow.

Automate Deployment and Testing for Efficiency

Automating your deployment and testing processes through the CLI can save a considerable amount of time and reduce the chances of manual errors. By setting up scripts to automate the deployment of Apex code and the execution of tests, you ensure a consistent and reliable development cycle, which is key to maintaining high-quality code.

Embrace Source-Driven Development for Consistency

Using SF CLI supports a source-driven development approach, encouraging the use of version control systems like Git for your Apex code. This method keeps a detailed record of all modifications, serving as a dependable reference for your codebase. It promotes better teamwork by simplifying the merging of code updates and ensures that every team member is working with the latest version.

Use Bulk Data Operations for Effective Testing

SF CLI’s ability to manage bulk data operations is invaluable for testing and debugging Apex code. It allows for efficient manipulation of large data sets, which is crucial for assessing how your Apex logic performs under various data conditions. Through CLI commands, you can insert, update, or delete records in bulk, streamlining the testing process and helping to identify any issues more quickly.

Integrate with CI/CD Pipelines for Streamlined Workflows

Integrating Salesforce CLI into your continuous integration (CI) and continuous deployment (CD) workflows automates the build, test, and deployment phases of your Apex code development. This not only ensures that your code is always of the highest quality but also speeds up the release of new features and bug fixes, keeping your project on the cutting edge.

Craft Custom Scripts for Routine Tasks

Developing custom CLI scripts for regularly performed tasks can dramatically reduce the time spent on repetitive activities. Whether it’s setting up new development environments, backing up data, or deploying specific components, these scripts can free up more time for actual development work, boosting productivity and efficiency.

Final Thoughts

By adopting these comprehensive strategies, developers can leverage the full capabilities of Salesforce CLI, making the Apex development process smoother, more accurate, and in line with industry best practices. It’s also beneficial to seek guidance from experts in the field. Engaging with a Certified Salesforce Consultant can provide profound insights into business process optimization and the impactful role consultants play within the Salesforce ecosystem.