Salesforce Apex triggers are a powerful tool for automating business logic in the background. We can perform some action when records are created, updated, or deleted. It is giving functionality to inject our custom code into the record save event life cycle. We should handle trigger code carefully. Many developers, especially those new to Apex or coming from other platforms, make mistakes. These mistakes can lead to performance issues, governor limit errors, data inconsistencies, or hard-to-maintain code.
In this post, we’ll explore the top mistakes developers make in Salesforce triggers, explain why they’re problematic, and show you how to avoid them using best practices.
1. Using Apex Trigger Instead of Salesforce Flow When Required
A developer loves to code, and they try to write code instead of using Salesforce Record Trigger Flow. Apex trigger is good, but our focus should be on a low-code approach. Why apex triggers not good always
- Hard to Maintain: Apex code needs a developer to make any changes, but Flows can be updated easily by admins without writing code.
- Reduced Agility: Business users can’t make changes themselves.
- Violates Low-Code Principle: Salesforce promotes declarative (clicks, not code) before programmatic solutions.
- Increased Technical Debt: Overuse of triggers increases the complexity of the org.
- Testing Overhead: Triggers require test classes; flows don’t.
In the above Apex trigger, we are updating the related contact object when the case is updated.
This complete logic can be easily written in Record Trigger flows. We can utilize the Get Record element, Decision element, and update elements to accomplish the same logic.
2. Not Using Trigger Framework
When developers write Apex triggers without a framework, the code:
- Becomes lengthy and cluttered with logic.
- Mixes DML, validation, and business logic directly in the trigger.
- Makes reuse and testing harder.
- Causes issues like duplicate DML, recursive trigger execution, or non-bulkified logic.
- Not following the separation of concern (SOLID) pattern
- Hard to create a unit test
Updating above trigger with the framework
The above enhancement makes our code
- Cleaner, modular, and reusable code.
- Easier to write unit tests for.
AccountTriggerHandler
- Prepared for scaling logic and handling more complex scenarios.
Check out our post Apex Trigger Code Optimization, for more details about trigger framework
3. Not Bulkifying the Trigger
The developer writes triggers assuming a single record will be processed. This will lead to governor limit violations (e.g., too many SOQL queries or DML statements) during bulk operations. Triggers will fail when we process large datasets (e.g., Data Loader imports).
We need to extract the SOQL and update statement from the loop and put it outside the loop to resolve this issue.
Check out the post How to Handle Bulkification in Apex to handle it properly.
4. Not Controlling Trigger Execution Context
Developers sometimes forget to check trigger execution context, like Trigger.isBefore, Trigger.isAfter, Trigger.isInsert, etc. This will lead to unintended logic execution in the wrong context, like updates running during inserts.
Solution:
Always check the trigger execution before executing any logic. Use Trigger.isBefore, Trigger.isAfter, Trigger.isInsert properly to implement trigger context.
trigger OpportunityTrigger on Opportunity (before insert, before update) {
if (Trigger.isBefore && Trigger.isInsert) {
// Insert-specific logic
} else if (Trigger.isBefore && Trigger.isUpdate) {
// Update-specific logic
}
}
5. Not handling Recursive Trigger Execution
In some business cases, we need to update the same object field in a transaction. It will create an infinite loop or a recursion issue, hitting governor limits or causing stack overflows. This will lead to a system crash or data loss.
In the trigger, we are updating the same account object. This will create a recursion issue. The code below is optimized code.
- Always use a trigger handler framework.
- Leverage static variables to control recursion.
- Avoid DML directly in triggers. Delegate to handler/service classes.
6. Using Hardcoding IDs or Values
When Salesforce developers work in a sandbox environment, they put hardcoded record type IDs, user IDs, or other values in triggers. This works well in that sandbox. But when they push this to another sandbox or production org, they see test code coverage errors. If test code was not covering that line of code, then we get runtime errors.
In the above example, the developer has used a hardcoded record type. This code might not execute in another org, as the record type ID can be different.
Instead of hardcoding ID, we can retrieve it at runtime and use it. See the below example for reference.
7. Not Handling Exceptions Properly
Error handling is important for Apex classes and triggers. If a developer fails to implement try-catch blocks, it can cause triggers to fail silently or disrupt the user experience. Due to this issue, users see unhandled errors, or data is left in an inconsistent state.
We can use specific exception classes to catch and analyze errors.
try {
update accounts;
} catch (DmlException e) {
for (Account a : Trigger.new) {
a.addError('Failed to update account: ' + e.getMessage());
}
}
8. Multiple Triggers on the Same Object
In some cases, developers create multiple triggers on the same objects. This multiple trigger issue can lead to
- Unpredictable execution order
- Redundant logic
- Hard-to-maintain or debug code
- Unexpected conflicts in code logic (e.g., validation in one trigger contradicts another)
OpportunityTrigger1 is updating stageName to Prospecting, and OpportunityTrigger2 is updating it to Qualification. What will be the stageName in the database depends on which trigger runs last. Salesforce does not guarantee trigger execution order, so the result is unpredictable.
Solution:
Create a separate handler class and use one trigger per object pattern. This will make a modular code approach.
9. Not Writing Test Classes or Poor Test Coverage
Not creating test classes or having low test coverage is a big problem when building Apex triggers in Salesforce. It can cause issues with deployment, make the system unstable, and lead to unexpected errors.
All logic branches of the trigger should be tested. Make sure code coverage is >75%.
10. Not Following Naming Conventions
Developers should adhere to best practices and established naming conventions. If a developer fails to use consistent naming conventions, it reduces code readability and maintainability. Future developers will not be able to understand code usage. By this code, developers cannot understand the purpose of this trigger.
trigger tr1 on Account (before insert, after update) {
// logic here
}
However, the code below provides the object and trigger event information. Any developer can understand its purpose.
trigger AccountTrigger on Account (before insert, after update) {
// logic here
}
Check out Naming Conventions for more details about the naming guidelines.
Summary
Salesforce Triggers are essential for automating business logic, but common mistakes like not bulkifying code, writing logic directly in triggers, missing recursion control, and hardcoding values can lead to performance issues and deployment failures.
By following Apex code best practices—such as using trigger handler classes, avoiding SOQL/DML in loops, handling errors properly, and understanding the order of execution—developers can write cleaner, more scalable, and more maintainable trigger logic. Avoiding these mistakes is key to building reliable and efficient Salesforce applications.
Need an Expert to Code Bulkification?
Check out our gig – development of Lightning Web Components, Apex, or integration—or contact us directly.
References
Related Posts
- The Ultimate Guide to Apex Order of Execution for Developers
- How to Handle Bulkification in Apex with Real-World Use Cases
- Enhancing Performance with File Compression in Apex
- Best Practices to Avoid Hardcoding in Apex for Cleaner Salesforce Code
- DML or SOQL Inside Loops
- Limiting data rows for lists
- Stop Describing every time and use Caching of the described object
- Use Filter in SOQL
- Optimize Trigger
- Bulkify Code
- Foreign Key Relationship in SOQL
- Handle Heap Size for Apex Code Optimization