ApexTools: TriggerHandler base class & field-change helpers
This document explains the TriggerHandler base class that ApexTools provides. For an index that includes the other tools, see the ApexTools Guide.
What You Can Do
Simply by extending the TriggerHandler base class, the entry-point processing from Triggers settles into a clean shape: "a trigger file that only declares the seven events" plus "a handler class that only writes the hooks you override". A built-in helper for "narrowing down to records where specific fields changed" lets afterUpdate filtering be written in a single line.
Inheritance: seven hooks plus andFinally
Each Handler extends the TriggerHandler base class and overrides only the hooks it needs. They are all protected virtual, so any hook you don't override does nothing. There are eight in total: seven matching the trigger events, plus andFinally, which runs last regardless of context.
| Hook | Signature |
|---|---|
beforeInsert | (List<SObject> newRecords) |
beforeUpdate | (Map<Id, SObject> newMap, Map<Id, SObject> oldMap) |
beforeDelete | (Map<Id, SObject> deletedMap) |
afterInsert | (Map<Id, SObject> newMap) |
afterUpdate | (Map<Id, SObject> newMap, Map<Id, SObject> oldMap) |
afterDelete | (Map<Id, SObject> deletedMap) |
afterUndelete | (Map<Id, SObject> undeletedMap) |
andFinally | () — always called last (in any context) |
Integrated With the Fixed Trigger.cls Pattern
The trigger file follows Salesforce convention and uses a fixed form. Declare all seven events and call the Handler in one line.
trigger Opportunity on Opportunity(
before insert,
before update,
before delete,
after insert,
after update,
after delete,
after undelete
) {
(new TriggerOppHandler()).execute();
}
🚨 On a custom object you cannot put
__cstraight into the trigger name. Apex identifiers may not contain a double underscore (Salesforce reserves it), sotrigger SalesActivity__c on SalesActivity__c(...)fails to deploy withInvalid character in identifier. Give the trigger a different name (SalesActivityTrigger, for instance — match the file name too). Standard objects are fine astrigger Opportunity on Opportunity(...).
The Handler side overrides only the hooks it needs.
public with sharing class TriggerOppHandler extends TriggerHandler {
protected override void afterInsert(Map<Id, SObject> newRecordsMap) {
Set<Id> opportunityIds = newRecordsMap.keySet();
(new CopyAccountIndustryToOpportunityUsecase(opportunityIds)).invoke();
}
}
Field-Change Detection Helpers
It's a frequent case to want to narrow afterUpdate to "records where specific fields changed". The TriggerHandler base class provides helpers for exactly that.
| Method | Return Type | Purpose |
|---|---|---|
getUpdatedRecordsWithChangedField(SObjectField field) | List<SObject> | Records where a single field changed |
getUpdatedRecordsWithChangedFields(List<SObjectField> fields) | List<SObject> | Records where any of multiple fields changed |
getUpdateRecordIdsWithChangedField(SObjectField field) | Set<Id> | Id version of the above |
getUpdateRecordIdsWithChangedFields(List<SObjectField> fields) | Set<Id> | Id version of the above |
Example usage:
public with sharing class TriggerOppHandler extends TriggerHandler {
protected override void afterUpdate(Map<Id, SObject> newMap, Map<Id, SObject> oldMap) {
Set<Id> needIds = this.getUpdateRecordIdsWithChangedFields(new List<SObjectField>{
Opportunity.AccountId,
Opportunity.StageName
});
(new RegenerateCollectionUsecase(needIds)).invoke();
}
}
The logic "do something only when a specific field changed" consolidates into a single line, and the Handler still keeps its slim "only condition checks and Usecase invocation" shape.
Other Notes
Hooks You Don't Override Just Do Nothing
The hooks on the TriggerHandler base class are all protected virtual and do nothing by default. A Handler that only wants to handle afterInsert overrides only afterInsert — no need to fill other hooks with empty methods.
When to Use andFinally
andFinally() is a hook that is always called last in any context. Use it for "processing that must always run last, regardless of the before / after type" (finalizing audit logs, wrapping up Trace, etc.). Most Handlers don't need it.
Read Next
- ApexTools Guide: the entry point that includes the other tools in ApexTools
- Handler-Usecase Architecture: the core Apex Stem architecture where
TriggerHandlershines - Apex Stem Introduction Guide: four steps with working code