100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Building a Trigger Handler

How to structure a one-trigger-per-object handler class with context-specific methods, recursion guards, and safe use of Trigger context variables.

PracticeIntermediate10 min readJul 10, 2026
Analogies

The Trigger Handler Pattern

A trigger handler pattern separates the trigger itself — a thin, declarative entry point — from the actual business logic, which lives in a dedicated handler class instantiated and invoked by the trigger. The trigger body becomes as simple as calling a single handler method, while the handler class contains methods like beforeInsert(), afterUpdate(), and beforeDelete() that map directly to the trigger context Salesforce fires. This separation exists because Apex allows multiple triggers per object but does not guarantee their execution order, so consolidating all logic for an object into one trigger delegating to one handler eliminates that non-determinism entirely.

🏏

Cricket analogy: A captain who calls every fielding position and bowling change from a single, coordinated game plan avoids the chaos of players independently deciding strategy — a single trigger delegating to one handler avoids the chaos of multiple uncoordinated triggers firing in unpredictable order.

Designing the Handler Class

A well-designed handler class implements distinct methods for each trigger context — beforeInsert, beforeUpdate, afterInsert, afterUpdate, beforeDelete, afterDelete, afterUndelete — often via a shared interface, so the trigger's dispatcher calls only the method relevant to the current Trigger.operationType. Before-context methods are used for field validation and default-value assignment on the in-memory records (since Trigger.new is mutable before insert/update), while after-context methods are used for operations touching related records via DML, since Trigger.new is read-only in after context and any downstream update requires a fresh SOQL/DML pass.

🏏

Cricket analogy: A team's pre-match warm-up focuses on fixing technique and stance before the ball is bowled, while the post-innings debrief reviews what happened and plans follow-up actions — beforeInsert validates/defaults fields in-memory while afterUpdate triggers downstream actions like tasks.

apex
public class AccountTriggerHandler implements TriggerHandlerInterface {
    public void beforeInsert(List<Account> newAccounts) {
        for (Account acc : newAccounts) {
            if (String.isBlank(acc.Rating)) {
                acc.Rating = 'Warm';
            }
        }
    }

    public void afterUpdate(Map<Id, Account> newMap, Map<Id, Account> oldMap) {
        List<Task> followUps = new List<Task>();
        for (Account acc : newMap.values()) {
            Account old = oldMap.get(acc.Id);
            if (acc.Rating == 'Hot' && old.Rating != 'Hot') {
                followUps.add(new Task(
                    WhatId = acc.Id,
                    Subject = 'Follow up: account upgraded to Hot',
                    ActivityDate = Date.today().addDays(1)
                ));
            }
        }
        if (!followUps.isEmpty()) {
            insert followUps;
        }
    }
}

Preventing Recursion

Because an afterUpdate method that itself performs update on the same object's records will re-fire the trigger, a robust handler guards against this with a static Boolean flag scoped to the transaction, checked at the very start of the handler's dispatch method and set to true before the sensitive logic runs; since static variables persist for the lifetime of a single transaction, this reliably blocks only the unwanted re-entrant call without affecting legitimate future updates. A more scalable variant uses a static Set<Id> of already-processed record Ids rather than a single flag, which allows the trigger to correctly process new records introduced later in the same transaction while still skipping ones already handled.

🏏

Cricket analogy: A DRS review system that flags 'this delivery has already been reviewed' prevents a team from requesting a second review on the same ball — a static flag prevents a trigger from re-processing the same records within one transaction.

Forgetting a recursion guard on an afterUpdate handler that itself performs a DML update on the same object is one of the fastest ways to hit CPU time limits or create duplicate related records (e.g., doubled Tasks or emails) in production.

Context Variables and Maps

Trigger.new, Trigger.old, Trigger.newMap, and Trigger.oldMap are the core context variables a handler consumes — newMap and oldMap are only populated in update, delete, and undelete contexts (never insert, since there's no 'old' state, and never delete for newMap since there's no 'new' state), and referencing the wrong one for the current context throws a runtime error. Comparing old and new field values field-by-field inside a loop is the standard way to detect exactly what changed and avoid firing downstream logic (like sending an email) when the update didn't actually touch the relevant field.

🏏

Cricket analogy: Comparing a batsman's stats before and after this innings tells the analyst exactly what changed — comparing Trigger.oldMap and Trigger.newMap field-by-field tells the handler exactly which fields actually changed.

  • Use one trigger per object that delegates immediately to a handler class implementing context-specific methods (beforeInsert, afterUpdate, etc.).
  • Before-context methods mutate Trigger.new directly for validation and defaults; after-context methods handle related-record DML since Trigger.new is read-only there.
  • Guard against recursive trigger execution with a static Boolean flag or a static Set<Id> of already-processed record Ids.
  • Trigger.newMap and Trigger.oldMap are only available in update/delete/undelete contexts, not insert (no oldMap) or delete (no newMap).
  • Compare old and new field values explicitly to detect real changes and avoid firing downstream logic unnecessarily.
  • Bulkify all handler methods — they receive Lists and Maps of potentially 200 records, never a single record.
  • A shared interface keeps handler classes consistent and makes the dispatcher logic reusable across objects.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ApexSalesforceStudyNotes#BuildingATriggerHandler#Building#Trigger#Handler#Pattern#StudyNotes#SkillVeris#ExamPrep