Skip to main content
New tool CRON Expression Builder — preview next run times before you schedule Apex. Open the builder →
Diagram illustrating common mistakes developers make when writing Apex triggers in Salesforce development
Apex

Top Mistakes Developers Make in Salesforce Apex Triggers

Non-bulkified code, several triggers on one object, hardcoded IDs and weak error handling are the Apex trigger mistakes that cost you performance, clean deployments and maintenance time.

The short answer

The Apex trigger mistakes that hurt most are writing the logic in the trigger body, running SOQL and DML inside loops, and putting several triggers on one object. All three lead to governor limit failures and code nobody wants to maintain. The fixes are bulkified code, explicit context checks, and a handler pattern that keeps the logic out of the trigger.

Key takeaways One trigger per object, with the work delegated to handler or service classes, keeps execution order predictable and the code modular. Collect record IDs into a Set and keep SOQL queries and DML operations outside your loops so every database interaction is bulkified. Static variables in a utility class, or a trigger framework, control re-entrancy and stop infinite recursion. Replace hardcoded IDs with runtime lookups by DeveloperName, Custom Metadata or Custom Settings so the code moves between orgs unchanged. Guard the logic with context variables such as Trigger.isBefore and Trigger.isInsert, and wrap DML in try-catch with addError so the user sees what failed.

Non-bulkified code, several triggers on one object, hardcoded IDs, weak error handling: these are the trigger mistakes I keep running into, and each one costs you performance, a clean deployment, or maintenance time.

Why triggers matter

Apex triggers run your custom logic during record save events: insert, update, delete. That power is the problem. A badly written trigger gives you governor limit failures, data changes nobody expected, and code the next developer cannot safely touch.

Top mistakes and how to fix them

1. Choosing Apex over Flow when a declarative solution fits

Reach for a Record-Triggered Flow on simple record automation. An admin can maintain it, and it is less debt. Apex earns its place when declarative tools cannot meet the requirement: complex processing, heavy logic, integrations.

2. No trigger framework (mixing logic in the trigger)

DML, SOQL, validations and business logic crammed into the trigger body are hard to test and hard to reuse. Keep one trigger per object and push the work into handler or service classes.

3. Not bulkifying the trigger

Never assume the trigger got one record. Handle the collection, and keep SOQL and DML out of the loop.

// Bad: SOQL inside loop for (Account a : Trigger.new) { Account acc = [SELECT Id, Name FROM Account WHERE Id = :a.Id]; // do work }

// Good: gather ids, then query once Set accIds = new Set(); for (Account a : Trigger.new) accIds.add(a.Id); Map accounts = new Map([SELECT Id, Name FROM Account WHERE Id IN :accIds]);

4. Not checking trigger context

Guard the logic with Trigger.isBefore / Trigger.isAfter and Trigger.isInsert / Trigger.isUpdate so it runs only in the context you meant it to.

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. Recursive trigger execution

Recursive updates can cause infinite loops. A handler framework, or static variables in a utility class, blocks the re-entry and keeps control of execution.

6. Hardcoding IDs or organization-specific values

Record type IDs, user IDs and other environment-specific values do not belong in code. Retrieve RecordType by DeveloperName, or keep the configuration in Custom Metadata or Custom Settings.

7. Poor exception handling

Wrap DML in try-catch and put a meaningful addError message on the record. The user gets something to act on, and the data is not left half processed.

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

Two triggers on one object fire in an order you do not control. One trigger per object, with a handler class orchestrating the logic, gets that order back.

9. Missing or weak test coverage

Cover every branch of the trigger logic and the edge cases with test classes. Assert on real outcomes, and stop the tests depending on hardcoded data.

10. Not following naming conventions

Name the trigger for the object it runs on (e.g., AccountTrigger), and name the handler to match. Readable names are what keep the code maintainable.

Best practices checklist

  • Use a Flow if a Flow can do it.
  • One trigger per object, logic in handler classes.
  • Bulkify queries and DML. Never in a loop.
  • Hold recursion back with static flags.
  • Look values up instead of hardcoding IDs.
  • Catch exceptions and show a readable error.
  • Test every branch.
  • Follow the org's naming and code style.

Conclusion: why this matters

None of this is clever work. It is the difference between a trigger that survives a data load and one that dies on a governor limit, between an org that is easy to support and one full of debt. Moving the right logic to Flow gives admins and architects more agility. Handler patterns and bulk-safe code give developers reliable deployments.

If you want help refactoring triggers or bulkifying code, a code review is a good place to start.

Frequently asked questions

Why should you only have one trigger per object in Salesforce?

Multiple triggers on the same object execute in an order you do not control. One trigger per object, delegating to handler classes, gives you a predictable order and one place to look when something goes wrong.

How do you avoid recursive triggers in Salesforce?

Use a trigger handler framework, or static variables in a utility class, to control execution flow across the transaction so the same logic cannot fire itself again.

When should you use Flow instead of an Apex trigger?

Use a Record-Triggered Flow for simple automation: it carries less technical debt and an admin can maintain it. Keep Apex triggers for complex logic, heavy data processing, or integrations that declarative tools cannot handle.

How do you bulkify an Apex trigger?

Write for collections instead of single records: gather the IDs into a Set, query the related records in a single SOQL statement, and run the DML outside the loop.

Newsletter

One email every Tuesday

New guides, tool updates, and the release-note changes that break things.

No spam. Unsubscribe in one click.

Comments

Loading comments...

Leave a Comment