Trigger vs Apex Class – When to Use What (Real Scenarios)

One of the most common questions in Salesforce development is: "Should I solve this with a trigger or an Apex class?" The answer depends on the scenario. Let's explore both with real-world examples.

Triggers: When to Use Them

Triggers automatically execute code when a DML event (insert, update, delete, undelete) happens on a record.

✅ Use Triggers For:

Real Scenario: Auto-update Account Rating

Requirement: When an Opportunity is won, automatically update the related Account's rating to "Hot".


trigger OpportunityTrigger on Opportunity (after update) {
    if (Trigger.isAfter && Trigger.isUpdate) {
        OpportunityTriggerHandler.updateAccountRating(Trigger.new, Trigger.oldMap);
    }
}

public class OpportunityTriggerHandler {
    public static void updateAccountRating(List<Opportunity> newOpps, Map<Id, Opportunity> oldMap) {
        Set<Id> accountIds = new Set<Id>();
        
        for (Opportunity opp : newOpps) {
            if (opp.StageName == 'Closed Won' && oldMap.get(opp.Id).StageName != 'Closed Won') {
                accountIds.add(opp.AccountId);
            }
        }
        
        List<Account> accountsToUpdate = new List<Account>();
        for (Account acc : [SELECT Id FROM Account WHERE Id IN :accountIds]) {
            acc.Rating = 'Hot';
            accountsToUpdate.add(acc);
        }
        
        update accountsToUpdate;
    }
}
  

Apex Classes: When to Use Them

Apex Classes contain reusable logic that can be called from triggers, API endpoints, or scheduled jobs.

✅ Use Apex Classes For:

Real Scenario: Send Notifications to Sales Managers

Requirement: When a lead is converted, send an email to the accountowner. You also want to run this from a custom button and a scheduled job.


// Reusable Apex Class
public class LeadConversionNotifier {
    public static void notifyAccountOwner(Set<Id> accountIds) {
        List<Account> accounts = [SELECT Id, OwnerId, Owner.Email FROM Account WHERE Id IN :accountIds];
        List<Messaging.SingleEmailMessage> emails = new List<Messaging.SingleEmailMessage>();
        
        for (Account acc : accounts) {
            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
            mail.setTargetObjectId(acc.OwnerId);
            mail.setSubject('New Lead Converted');
            mail.setPlainTextBody('A new lead has been converted to an account.');
            emails.add(mail);
        }
        
        if (!emails.isEmpty()) {
            Messaging.sendEmail(emails);
        }
    }
}

// Called from trigger
trigger LeadTrigger on Lead (after update) {
    if (Trigger.isAfter && Trigger.isUpdate) {
        Set<Id> convertedAccounts = new Set<Id>();
        
        for (Lead lead : Trigger.new) {
            if (lead.IsConverted && !Trigger.oldMap.get(lead.Id).IsConverted) {
                convertedAccounts.add(lead.ConvertedAccountId);
            }
        }
        
        if (!convertedAccounts.isEmpty()) {
            LeadConversionNotifier.notifyAccountOwner(convertedAccounts);
        }
    }
}

// Called from custom button/scheduled job
global class LeadConversionBatch implements Schedulable {
    global void execute(SchedulableContext ctx) {
        Set<Id> recentlyConverted = new Set<Id>();
        // ... fetch recently converted leads
        LeadConversionNotifier.notifyAccountOwner(recentlyConverted);
    }
}
  

Comparison Table

Feature Trigger Apex Class
Auto-executes on DML ✅ Yes ❌ No
Manually invoked ❌ No ✅ Yes
Used for multiple objects ❌ No (one per object) ✅ Yes (reusable)
Testable ✅ Yes ✅ Yes

Best Practice Pattern

Thin Trigger + Fat Class: Keep triggers thin, delegate business logic to reusable classes.


// GOOD: Trigger delegates to handler class
trigger AccountTrigger on Account (before insert, before update, after insert, after update) {
    if (Trigger.isBefore) {
        AccountHandler.beforeSave(Trigger.new, Trigger.oldMap);
    }
    if (Trigger.isAfter) {
        AccountHandler.afterSave(Trigger.new, Trigger.oldMap);
    }
}
  

Key Takeaways