I'm running into a stack depth issue which is puzzling at best, but maybe because I'm fairly new to Apex. I have an interface built to enforce some structure to trigger calls, which is then implemented by a virtual class, which begins building the correct constructors and executing the correct methods [takes place in the execute() method].

When I insert a new Opportunity record, it gives me a stack depth error UNLESS either of the following is true:

  1. I remove the "public void" methods [e.g. createScheduleLines()] and instead put that code directly in the "public override void" methods [e.g. OnAfterInsert()]
  2. I remove all "public void" methods that aren't "createScheduleLines()"

Why would this only work for one method reference and/or when there are no method references (but the code is built in)...

Is this because these "public void" methods need to be static so as to only be called when required?

Below is my code:

public interface triggerInterface {
    void execute();
    void onBeforeInsert();
    void onBeforeUpdate();
    void onBeforeDelete();
    void onAfterInsert();
    void onAfterUpdate();
    void onAfterDelete();

triggerBase implements triggerInterface

public virtual class triggerBase implements triggerInterface {

    protected boolean isBefore;
    protected boolean isAfter;
    protected boolean isInsert;
    protected boolean isUpdate;
    protected boolean isDelete;
    protected boolean isUndelete;

    public triggerBase(boolean isBefore, boolean isAfter, boolean isInsert, boolean isUpdate, boolean isDelete, boolean isUndelete) {
        this.isBefore = isBefore;
        this.isAfter = isAfter;
        this.isInsert = isInsert;
        this.isUpdate = isUpdate;
        this.isDelete = isDelete;
        this.isUndelete = isUndelete;

    public triggerBase() {}

    //** Responsible for launching execution of business logic
    public virtual void execute() {
        if (this.isBefore) {
            if (this.isInsert) {
                System.debug('***** triggerBase.OnBeforeInsert()');
            } else if (this.isUpdate) {
                System.debug('***** triggerBase.OnBeforeUpdate()');
            } else if (this.isDelete) {
                System.debug('***** triggerBase.OnBeforeDelete()');
        } else if (this.isAfter) {
            if (this.isInsert) {
                System.debug('***** triggerBase.OnAfterInsert()');
            } else if (this.isUpdate) {
                System.debug('***** triggerBase.OnAfterUpdate()');
            } else if (this.isDelete) {
                System.debug('***** triggerBase.OnAfterDelete()');
            } else if (this.isUndelete) {
                System.debug('***** triggerBase.OnAfterUndelete()');

    public virtual void onBeforeInsert() {} //** Has access to newMap
    public virtual void onBeforeUpdate() {} //** Has access to oldMap and newMap
    public virtual void onBeforeDelete() {} //** Has access to oldMap
    public virtual void onAfterInsert() {} //** Has access to newMap
    public virtual void onAfterUpdate() {} //** Has access to oldMap and newMap
    public virtual void onAfterDelete() {} //** Has access to oldMap
    public virtual void onAfterUndelete() {} //** Has access to newMap

OpportunityTrigger then calls OpportunityHandler, which extends triggerBase

public class OpportunityHandler extends triggerBase {
    private Map<Id, Opportunity> newMap;
    private Map<Id, Opportunity> oldMap;
    private Boolean isHandlerExecuting = false;
    private Integer BatchSize = 0;
    public OpportunityHandler(Boolean isBefore, Boolean isAfter, 
            Boolean isInsert, Boolean isUpdate, Boolean isDelete, Boolean isUndelete, 
            Map<Id, Opportunity> oldMap, Map<Id, Opportunity> newMap, 
            Boolean isExecuting, Integer size) {
        super(isBefore, isAfter, isInsert, isUpdate, isDelete);
        this.newMap = newMap;
        this.oldMap = oldMap;
        this.isHandlerExecuting = isExecuting;
        this.BatchSize = size;
    public override void OnAfterInsert() {
        System.debug('***** OpportunityHandler.OnAfterInsert()');
    public override void OnAfterUpdate() {
        System.debug('***** OpportunityHandler.OnAfterUpdate()');
    public override void OnBeforeDelete() {
        System.debug('***** OpportunityHandler.OnBeforeDelete()');
    //** When you insert an Opportunity(Socket), it should create a Schedule_Line__c for all current Opportunity(Socket)
    public void createScheduleLines() {
        System.debug('***** OpportunityHandler.OnAfterInsert().createScheduleLines()');
        //** Populate a list with all Schedule__c related to Opportunity(Board) where there are new Opportunity(Socket)
        List<Schedule__c> schs = [
            SELECT Id, Board_Name__c
            FROM Schedule__c
            WHERE Board_Name__c IN :this.newMap.keySet()];
        //** Create new Schedule_Line__c for each Schedule__c on the Opportunity(Board) where there are new Opportunity(Socket)
        List<Schedule_Line__c> schlnToInsert = new List<Schedule_Line__c>();
        for (Opportunity socket : this.newMap.values()) {
            for (Schedule__c sch : schs) {
                if (socket.Board_Name__c == sch.Board_Name__c) {
                    Schedule_Line__c newSL = new Schedule_Line__c();
                        newSL.Schedule__c = sch.Id;
                        newSL.Socket_Name__c = socket.Id;
        //** Insert all Schedule_Line__c where there are new Opportunity(Socket)
        List<Database.SaveResult> schlnToInsertResult = Database.insert(schlnToInsert, false);
    //** When you update an Opportunity(Socket), it should force all related Schedule_Line__c.Value__c to re-calculate
    public void adjustScheduleLines() {
        System.debug('***** OpportunityHandler.OnAfterUpdate().adjustScheduleLines()');
        //** Populate a list with all Schedule__c where Opportunity.Socket_Value__c changed
        List<Id> socketIds = new List<Id>();
        for (Opportunity socket : this.newMap.values()) {
            if (socket.Socket_Value__c != this.oldMap.get(socket.Id).Socket_Value__c) {
        //** Populate a list with all Schedule_Line__c where Opportunity.Socket_Value__c changed
        List<Schedule_Line__c> schlns = [
            SELECT Id
            FROM Schedule_Line__c
            WHERE Socket_Name__c IN :socketIds];
        //** Set Schedule_Line__c.Value__c to 0 to re-trigger the Workflow Rule which updates the value from the Opportunity(Socket)
        List<Schedule_Line__c> schlnToUpdate = new List<Schedule_Line__c>();
        for (Schedule_Line__c schln : schlns) {
            schln.Value__c = 0;
        //** Update all Schedule_Line__c where Opportunity.Socket_Value__c changed
        List<Database.SaveResult> schlnToUpdateResult = Database.update(schlnToUpdate, false);
    //** When you delete an Opportunity(Socket), it should delete all related Schedule_Line__c
    public void removeScheduleLines() {
        System.debug('***** OpportunityHandler.OnBeforeDelete().removeScheduleLines()');
        //** Populate a list with all Schedule_Line__c where Opportunity(Socket) is marked for deletion
        List<Schedule_Line__c> schlnToDelete = [
            SELECT Id
            FROM Schedule_Line__c
            WHERE Socket_Name__c IN :this.oldMap.keySet()];
        //** Delete all Schedule_Line__c where Opportunity(Socket) is marked for deletion
        List<Database.DeleteResult> schlnToDeleteResult = Database.delete(schlnToDelete, false);
    //** When you update an Opportunity(Board) from Active to Closed it should update 'Design In' Opportunity(Socket)
    public void processDesignInClaim() {
        List<OpportunityLineItem> oliPrimary = new List<OpportunityLineItem>();

1 Answer 1


I am confused as to what you are really trying to accomplish with this structure. You don't need to create any logic for the actual order of execution (which, correct me if I am wrong, it appears you are trying to do here). Triggers have a very specific order of execution. It will actually run through your trigger several times to hit before and after logic.

Wouldn't it be much easier to do something like:

trigger OpportunityTrigger on Opportunity (after insert, after update, before delete) {
    if(Trigger.isAfter && Trigger.isInsert){

    if(Trigger.isAfter && Trigger.isUpdate){
        OpportunityHandler.adjustScheduleLines(Trigger.newMap, Trigger.oldMap);

    if(Trigger.isBefore && Trigger.isDelete){


public class OpportunityHandler{

    public static void createScheduleLines(Map<Id, Opportunity> newMap) {
        // logic

    public static void adjustScheduleLines(Map<Id, Opportunity> newMap, Map<Id, Opportunity> oldMap) {
        // logic

    public static void removeScheduleLines(Map<Id, Opportunity> oldMap) {
        // logic


Let Salesforce worry about the order they are executed.

  • 1
    Honestly, my triggerBase and triggerInterface were holdovers from my predecessor. I still haven't quite figured out what the benefit of it was and keep seeing your above format all over the interwebs. I guess I figured there was logic to why he did it in the first place, so I'd keep it. Thoughts? Sep 25, 2013 at 16:02
  • 3
    There are a number of trigger frameworks for salesforce out there, and how useful they are is very situational. I'm typically of the opinion that this approach of just calling static methods in the order you want is easiest, but others may disagree. Sep 25, 2013 at 16:44
  • 3
    I would agree that although the best approach is subjective, this approach is highly used and generally accepted. It is the easiest and most straight forward approach IMO. Sep 25, 2013 at 16:48
  • As others have suggested, there are always multiple approaches to this sort of thing. I typically prefer the simplest approach. Like I mentioned in my answer, I am not sure exactly what the purpose of your code is so I can't say if you should remove it or not, but from my experience it is easier to use the approach I outlined above. Sep 25, 2013 at 17:37
  • Honestly, I think the purpose was to simplify the code in the trigger itself. It's very copy/paste this way. However, a downside is that it's not so simple to call @future methods, so I'm going to abandon it... Sep 25, 2013 at 17:41

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .