Apex Triggers the Definitive Guide

Updated: Dec 20, 2021

Apex Triggers execute before or after a database insert, update, delete, merge, upsert, undelete, or any combination thereof. Salesforce.com allows Triggers for most standard and all custom objects if running Unlimited, Developer, Enterprise, or Database.com editions of Salesforce.com.

Apex Triggers allow developers to programmatically check for duplicate records, update fields across objects, automate the creation of new records based on criteria, and much more. For example, if I wanted to check for duplicity on the user email field every time a user was created or updated I would write a trigger and assign the "before insert, before update" operations to it. This would allow me to query the database and throw an exception if a duplicate is found before saving to the database.

Trigger:


trigger triggerName on Object( operation/s ) {
/* -- Execute Trigger Logic Here -- */
}



Governor Limits

Salesforce.com is a multi-tenant environment. In an effort not to jeopardize the availability of shared resources, Salesforce.com enforces governor limits which will return a runtime exception and the code executing will fail. For details on all Salesforce.com governor limits refer to Winter 12' Governor Limits Quick Reference Guide.

Apex Governor Limits

Apex Triggers are bound by the Apex governor limits. As of Salesforce.com's Winter 12 Release they are as follows:

DescriptionLimitTotal number of SOQL queries issued100Total number of SOQL queries issued for Batch Apex and future methods200Total number of records retrieved by SOQL queries50,000Total number of SOSL queries issued20Total number of records retrieved by a single SOSL query200Total number of DML statements issued150Total number of records processed as a result of DML statements, Approval.process, or database.emptyRecycleBin10,000Total number of executed code statements200,000Total number of executed code statements for Batch Apex and future methods1,000,000Total heap size3 MBTotal heap size for Batch Apex and future methods6 MBTotal stack depth for any Apex invocation that recursively fires triggers due to insert,update, or delete statements16For loop list batch size200Total number of callouts (HTTP requests or Web services calls) in a request10Maximum timeout for all callouts (HTTP requests or Web services calls) in a request120 secondsDefault timeout of callouts (HTTP requests or Web services calls) in a request10 secondsTotal number of methods with the future annotation allowed per Apex invocation10Maximum size of callout request or response (HTTP request or Web services call)3 MBTotal number of sendEmail methods allowed10Total number of describes allowed100Total number of test classes that can be queued per a 24–hour periodThe greater of 500 or 10 multiplied by the number of test classes in the organization

Best Practices

The Force.com platform provides developers the flexibility to write and easily deploy Apex Triggers. Apex Triggers, like any powerful tool, need to be written with solid logic and thoroughly tested before deploying into a production environment.

  • Use one Apex Trigger for every object; all logic should be handled by an external Apex Class.

  • Keep Apex Trigger logic simple, and thoroughly documented with code comments.

  • Write Test Classes that execute the code in realistic scenarios.

  • Write Apex Triggers for bulk processing (up to 200) records at a time.

  • DML statements should execute on collections not individual records.

  • Add field validations on both the before and after operations.

  • Use static variables to persist on both the before and after operations.

  • Do not execute SOQL queries inside for loops, or inside of any before or after operations.

Design Pattern

Utilize consistent design patterns for Apex Triggers as you would any other code and the code will be more likely to run efficiently, within governor limits, and bug-free.

Example Trigger

An Apex Trigger should be written so that every operation calls a method in an external Apex Class. By doing so code can easily be added or removed, and variables can be used multiple times without additional queries to the database.

Trigger


trigger objectTrigger on Object (after delete, after insert, after undelete, after update, before delete, before insert, before update) {


  objectTriggerHandler handler = new objectHandler();


  /* Before Insert */
  if(Trigger.isInsert && Trigger.isBefore){
    handler.OnBeforeInsert(Trigger.new);
  }
  /* After Insert */
  else if(Trigger.isInsert && Trigger.isAfter){
    handler.OnAfterInsert(Trigger.new);
  }
  /* Before Update */
  else if(Trigger.isUpdate && Trigger.isBefore){
    handler.OnBeforeUpdate(Trigger.old, Trigger.new, Trigger.newMap);
  }
  /* After Update */
  else if(Trigger.isUpdate && Trigger.isAfter){
    handler.OnAfterUpdate(Trigger.old, Trigger.new, Trigger.newMap);
  }
  /* Before Delete */
  else if(Trigger.isDelete && Trigger.isBefore){
    handler.OnBeforeDelete(Trigger.old, Trigger.oldMap);
  }
  /* After Delete */
  else if(Trigger.isDelete && Trigger.isAfter){
    handler.OnAfterDelete(Trigger.old, Trigger.oldMap);
  }


  /* After Undelete */
  else if(Trigger.isUnDelete){
    handler.OnUndelete(Trigger.new);
  }


}

Example Trigger Handler Class

The Apex Trigger template above calls a handler class to execute the trigger logic, the trigger handler class should be defined as follows:

Apex


public with sharing class ObjectTriggerHandler {


  private boolean m_isExecuting = false;


  public ObjectTriggerHandler(boolean isExecuting){
    m_isExecuting = isExecuting;
  }


  public void OnBeforeInsert(Object[] newObjects){
    // EXECUTE BEFORE INSERT LOGIC
  }


  public void OnAfterInsert(Object[] newObjects){
    // EXECUTE AFTER INSERT LOGIC
  }


  public void OnBeforeUpdate(Object[] oldObjects, Object[] updatedObjects, MapObjectMap){
    // BEFORE UPDATE LOGIC
  }


  public void OnAfterUpdate(Object[] oldObjects, Object[] updatedObjects, MapObjectMap){
    // AFTER UPDATE LOGIC
  }


  public void OnBeforeDelete(Object[] ObjectsToDelete, MapObjectMap){
    // BEFORE DELETE LOGIC
  }


  public void OnAfterDelete(Object[] deletedObjects, MapObjectMap){
    // AFTER DELETE LOGIC
  }


  public void OnUndelete(Object[] restoredObjects){
    // AFTER UNDELETE LOGIC
  }


  public boolean IsTriggerContext{
    get{ return m_isExecuting;}
  }
}

Recursive Triggers

Prevent Apex Triggers from running recursively and thus exceeding governor limits, include the following statement to the Apex Trigger handler class.

Apex


public static boolean firstRun = true;


Insert the following into the OnAfterUpdateMethod.
if (firstRun) {
  firstRun = false;
}
else {
  System.debug('Already ran!');
  return;
}

Many excellent contributions by experienced Salesforce.com developers made this article possible. Please use the references to their sites and related blog posts below for more information. Happy Coding!

Other Resources

  • Mike Leach: A Simple Trigger Template for Salesforce (2010/07/08)

  • Ray Dehler:Prevent an Apex Trigger from Executing Twice (2011/04/13)

  • Jeff Douglas: Force.com Programming Best Practices (2010/10/21)

  • Developer Force - Apex Code Best Practices

4 views0 comments

Recent Posts

See All

Time

Time is like a river. You cannot touch the same water twice, because the flow that has passed will never pass again. Enjoy every moment in life!

Self-Growth Challenge

Start: Rich Dad Poor Dad Day 1: How to talk to anyone Day 2: The power of habit Day 3: Love Languages Day 4: Think and Grow Rich Day 5: 5 AM Club Day 6: The 7 Habits of highly effective people Day 7: