Top Mistakes Developers Make in Salesforce Apex Triggers

Common Apex trigger mistakes (non-bulkified code, multiple triggers, hardcoded IDs, poor error handling) cause performance, deployment, and maintenance problems. This post explains the top pitfalls and how to avoid them with concrete best practices.

Why triggers matter

Salesforce Apex triggers let you run custom logic during record save events (insert, update, delete). They are powerful, but poorly written triggers lead to governor limit failures, unexpected data changes, and hard-to-maintain code. Below are the top mistakes developers make and practical ways to avoid them.

Top mistakes and how to fix them

1. Choosing Apex over Flow when a declarative solution fits

Prefer Record-Triggered Flows for simple record automation. Flows are easier for admins to maintain and reduce technical debt. Use Apex only when declarative tools can’t meet requirements (complex processing, heavy logic, or integrations).

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

Putting DML, SOQL, validations and business logic inside the trigger body makes code hard to test and reuse. Use a single-trigger-per-object pattern and delegate work to handler/service classes for separation of concerns.

3. Not bulkifying the trigger

Never assume a trigger will receive one record. Always handle collections and move SOQL/DML outside loops.

// 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

Always guard your logic with Trigger.isBefore / Trigger.isAfter and Trigger.isInsert / Trigger.isUpdate so code runs only in the intended 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. Recursive trigger execution

Recursive updates can cause infinite loops. Use a handler framework or static variables (in a utility class) to prevent re-entrancy and control execution.

6. Hardcoding IDs or organization-specific values

Never hardcode record type IDs, user IDs, or other environment-specific values. Retrieve RecordType by DeveloperName or use Custom Metadata/Custom Settings to store environment-agnostic configuration.

7. Poor exception handling

Wrap DML in try-catch, log or add meaningful addError messages to records so users receive actionable feedback and data isn’t 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

Multiple triggers introduce unpredictable execution order. Use one trigger per object and a handler class to orchestrate the logic in a predictable way.

9. Missing or weak test coverage

Write test classes covering all branches of your trigger logic and edge cases. Aim for robust assertions and avoid brittle tests that rely on hardcoded data.

10. Not following naming conventions

Use meaningful trigger names (e.g., AccountTrigger) and handler classes. Good naming improves readability and maintainability.

Best practices checklist

  • Prefer declarative automation (Flow) where possible.
  • Use one-trigger-per-object with handler/service classes.
  • Bulkify queries and DML; never put them in loops.
  • Control recursion with static flags.
  • Avoid hardcoded IDs; use metadata or runtime lookups.
  • Handle exceptions and provide user-friendly errors.
  • Write comprehensive unit tests for all branches.
  • Follow naming and code-style conventions.

Conclusion: Why this matters

Correct trigger design prevents governor-limit errors, improves performance, reduces technical debt, and makes your org easier to support. For admins and architects, moving appropriate logic to Flow increases agility. For developers, using handler patterns and bulk-safe code ensures reliable deployments.

If you want help refactoring triggers or bulkifying code, consider reaching out to an expert for a code review or engagement.