Contents

Abstract

Apex code is the Force.com programming language to write custom, robust business logic. But as with any programming language, there are key coding principles or best practices that help write efficient, scalable code. This article illustrates many of the key Best Practices for writing and designing Apex Code solutions on the Force.com platform.

Best Practice #1: Bulkify your Code

The term ‘bulkifying’ apex code refers to the concept of making sure the apex code properly handles more than 1 record at a time. When a batch of records initiate Apex, a single instance of that apex code is executed, but it needs to handle all of the records in that given batch. For example, a trigger could be invoked by an API call that inserted a batch of records. Or a custom Apex Web Service. So if a batch of records invokes the the same apex code, all of those records need to be processed in a ‘bulkified’ manner.

Here is an example of a poorly written code that only handles one record:

trigger accountTestTrggr on Account (before insert, before update) {

   //This only handles the first record in the Trigger.new collection
   //But if more than 1 Account initiated this trigger, those additional records
   //will not be processed
   Account acct = Trigger.new[0];
   List<Contact> contacts = [select id, salutation, firstname, lastname, email 
              from Contact where accountId = :acct.Id];
   
}

The issue is that only one Account record is handled because the code explicitly handles on the first record in the Trigger.new collection by using this syntax: Trigger.new[0]. Instead, the trigger should properly handle the entire collection of Accounts in the Trigger.new collection.

Here is a sample of how to handle all incoming records:

trigger accountTestTrggr on Account (before insert, before update) {

   List<String> accountNames = new List<String>{};
 
   //Loop through all records in the Trigger.new collection
   for(Account a: Trigger.new){
      //Concatenate the Name and billingState into the Description field
      a.Description = a.Name + ':' + a.BillingState
   }
   
}

Notice how this revised version of the code iterates across the entire Trigger.new collection with a FOR Loop. Now if this trigger is invoked with a single Account or up to 200 Accounts, all records will be properly processed.

Best Practice #2: Avoid SOQL Queries inside FOR Loops

The previous Best Practice talked about the importance of handling all incoming records in a bulk manner. The example was to use a FOR loop to iterate across all of the records in the Trigger.new collection. But it is important to avoid having SOQL queries inside FOR loops. There is a governor limit that enforces a maximum number of SOQL Queries and if a query is inside a FOR loop, that governor limit is reached inefficiently and unnecessarily. If a query is inside a FOR loop, a query will be executed for each iteration of the loop. But to write more efficient and scalable code, its ideal to move the SOQL query outside of the for loop and retrieve all the necessary data in a single query.

Here is an example of a having a query inside a FOR loop:

trigger accountTestTrggr on Account (before insert, before update) {
	  
      //For loop to iterate through all the incoming Account records
      for(Account a: Trigger.new){
      	  /*
      	    THIS FOLLOWING QUERY IS INEFFICIENT AND DOESN'T SCALE
      	  	    Since the SOQL Query for related Contacts is within the FOR loop, if this trigger is initiated 
      	  	    with more than 20 records, the trigger will exceed the trigger governor limit
                    of maximum 20 SOQL Queries.
      	  */
      	  List<Contact> contacts = [select id, salutation, firstname, lastname, email 
                        from Contact where accountId = :a.Id];
 		  
 		  for(Contact c: contacts){
 		  	  System.debug('Contact Id[' + c.Id + '], FirstName[' + c.firstname + '], LastName[' + c.lastname +']');
 		  	  c.Description=c.salutation + ' ' + c.firstName + ' ' + c.lastname;
 		  	   /*
      	            THIS FOLLOWING DML STATEMENT IS INEFFICIENT AND DOESN'T SCALE
      	  	    Since the UPDATE dml operation is within the FOR loop, if this trigger is initiated 
      	  	    with more than 20 records, the trigger will exceed the trigger governor limit 
                    of maximum 20 DML Operations.
      	      */
      	      
 		  	  update c;
 		  }    	  
      }
}

Since there is a SOQL Query within the FOR loop that iterates across all the Account objects that initiated this trigger, a query will be executed for each Account. And by default, a trigger gets a maximum of 20 SOQL queries before exceeding that governor limit. So if this trigger is invoked by a batch of more than 20 Account records, the governor limit will throw a runtime exception.

I do want to elaborate that this is true for DML operations too. Meaning, try to avoid having DML operations (insert, update, delete) inside a FOR Loop since that will also unnecessarily exceed the governor limit pertaining to DML operations. In this example, a trigger only allows 20 DML operations so a governor limit will be exceeded after the 20th contact is updated.

Here is the optimal way to ‘bulkify’ the code to efficiently query the Contacts in a single query and only perform a single Update DML operation.

trigger accountTestTrggr on Account (before insert, before update) {
	  //This queries all Contacts related to the incoming Account records in a single SOQL query.
	  //This is also an example of how to use child relationships in SOQL
	  List<Account> accountsWithContacts = [select id, name, (select id, salutation, description, firstname, lastname, 
	 	 	email from Contacts) from Account where Id IN :Trigger.newMap.keySet()];
	  
	  List<Contact> contactsToUpdate = new List<Contact>{};
	  //For loop to iterate through all the queried Account records 
      for(Account a: accountsWithContacts){
      	  //Use the child relationships dot syntax to access the related Contacts
      	  for(Contact c: a.Contacts){
 		  	  System.debug('Contact Id[' + c.Id + '], FirstName[' + c.firstname + '], LastName[' + c.lastname +']');
 		  	  c.Description=c.salutation + ' ' + c.firstName + ' ' + c.lastname; 
 		  	  contactsToUpdate.add(c);
 		  }    	  
      }
      
      //Now outside the FOR Loop, perform a single Update DML statement. 
      update contactsToUpdate;
}

Now if this trigger is invoked with a single Account or up to 200 Accounts, only 1 SOQL query and 1 Update statement is executed.


Best Practice #3: Bulkify your helper Methods

This best practice is similar to the previous best practice - in that you want to make sure any code that runs a query or DML operation does it in a bulk manner and doesn't execute within an iteration or for loop. Again, by executing queries or dml operations within an iteration adds risk to exceeding the related governor limits. And this is true for any helper or utility methods an apex request executes. As discussed in the [SFDC:ApexGovernorLimits Apex Governor Limits] Deep-Dive article, governor limits are calculated in runtime and the key criteria is how the apex request was initiated. But after the request is initiated (ie Trigger, Visualforce page, ect), any apex code executed in that transaction applies and shares the governor limits. So if a trigger uses some apex methods written in a helper class, its important that those shared apex methods are properly designed to handle bulk records. These methods should be written to be invoked with a set of records, especially if the method has a SOQL query or DML operation.

For example, if the apex method performs a SOQL Query, that method should receive a collection (Array, List, Set, ect) of records so when it performs the query, it can perform for it for all records in the apex transaction. Otherwise, if the apex method is called individually for each record being processed, the apex transaction will inefficiently run queries and possibly exceed the number of queries allowed in that transaction. The same is true for DML statements in apex methods.

So please make sure any utility or helper methods are efficiently written to handle collections of records. This will avoid unnecessarily executing inefficient queries and dml operations.

Best Practice #4: Using Collections, Streamling Queries, and Efficient For Loops

It is important to use Apex Collections to efficiently query data and store the data in memory. A combination of using collections and streamling SOQL queries can substantially help efficiently write Apex code and avoid governor limits.

Here is a sample that inefficiently uses Collections:

trigger accountTrigger on Account (before delete, before insert, before update) {
 
    //This code inefficiently queries the Opportunity object in two seperate queries
    List<Opportunity> opptysClosedLost = [select id, name, closedate, stagename
            from Opportunity where  
            accountId IN :Trigger.newMap.keySet() and StageName='Closed - Lost'];
    
    List<Opportunity> opptysClosedWon = [select id, name, closedate, stagename
            from Opportunity where  
            accountId IN :Trigger.newMap.keySet() and StageName='Closed - Won'];
    
    for(Account a : Trigger.new){
    	
    	//This code inefficiently has two inner FOR loops
    	//Redundantly processes the List of Opportunity Lost
        for(Opportunity o: opptysClosedLost){	
    		if(o.accountid == a.id)
    		   System.debug('Do more logic here...');
        }
        
        //Redundantly processes the List of Opportunity Won
        for(Opportunity o: opptysClosedWon){	
    		if(o.accountid == a.id)
    		   System.debug('Do more logic here...');
        }
    }
}

The main issue with the previous snippet is the unnecessary querying of the Opportunity records in two separate queries. Use the power of the SOQL Where clause to query all data needed in a single query. Secondly, there are two inner FOR loops that redundantly loop through the list of Opportunity records just trying to find the ones related to a specific Account.

trigger accountTrigger on Account (before delete, before insert, before update) {
 
     //This code efficiently queries all related Closed Lost and
     //Closed Won opportunities in a single query.
    List<Account> accountWithOpptys = [select id, name, (select id, name, closedate, 
         stagename  from Opportunities  where accountId IN :Trigger.newMap.keySet() 
         and  (StageName='Closed - Lost' or StageName = 'Closed - Won')) 
         from Account where Id IN :Trigger.newMap.keySet()];
    
    //Loop through Accounts only once
    for(Account a : accountWithOpptys){
    	
    	 //Loop through related Opportunities only once
    	 for(Opportunity o: a.Opportunities){
    	 	if(o.StageName == 'Closed - Won'){
    	 		System.debug('Opportunity Closed Won...do some more logic here...');
    	 	}else if(o.StageName =='Closed - Lost'){
    	 		System.debug('Opportunity Closed Lost...do some more logic here...');
    	 	}
    	 }
    
   }
}

This revised sample only executes 1 query for all related Opportunities and only have 1 inner FOR loop to apply the same logic, but in a much more efficient, governor-friendly manner.

Best Practice #5: Streamling Multiple Triggers on same Object

It is important to avoid redundancies and inefficiencies when deploying multiple triggers on the same object. If developed independently, it is possible to have redundant queries that query the same dataset or possible have redundant FOR statements.

NOTE: It is very important to detail exactly how governor limits are applied when multiple triggers are deployed on the same object. For starters, you do not have any explicit control over which trigger gets initiated first. Secondly, each trigger that is invoked does not get its own governor limits. Instead, the first trigger defines the entry point and therefore defines the context of the governor limit calculations. But all code that is processed, including the additional triggers, share those available resources. So instead of 1 trigger get a maximum of 20 queries, all triggers on that same object will share those 20 queries. That is why it is critical to ensure that the multiple triggers are efficient and no redundancies exist.

Best Practice #6: Querying Large Data Sets

SOQL queries that return multiple records can only be used if the query results do not exceed 1,000 records, the maximum size limit of a list. If the query results return more than 1,000 records, then a SOQL query for loop must be used instead, since it can process multiple batches of records through the use of internal calls to query and queryMore.

For example, if the results are too large, the syntax below causes a runtime exception:

Account[] accts = [SELECT id FROM account];

Instead, use a SOQL query for loop as in one of the following examples:

// Use this format for efficiency if you are executing DML statements 
// within the for loop
for (List<Account> accts : [SELECT id, name FROM account
                            WHERE name LIKE 'Acme']) {
    // Your code here
    update accts;
}

Best Practice #7: Use of the Limits Apex Methods for debugging

Apex has a System class called Limits that lets you output debug messages for each governor limit. There are two versions of every method: the first returns the amount of the resource that has been used in the current context, while the second version contains the word limit and returns the total amount of the resource that is available for that context. The following example shows how to embed these types of statements in your code. And then using either the System Log or Debug Logs, you can evaluate the output to see how the specific code is performing against the governor limits. This example revisits the inefficient code example in Best Practice #2. The first screenshot has the apex code with the embedded Limit statements.

trigger accountLimitExample on Account (after delete, after insert, after update) {

    System.debug('Total Number of SOQL Queries allowed in this apex code context: ' +  Limits.getLimitQueries());
    System.debug('Total Number of records that can be queried  in this apex code context: ' +  Limits.getLimitDmlRows());
    System.debug('Total Number of DML statements allowed in this apex code context: ' +  Limits.getLimitDmlStatements() );
    System.debug('Total Number of script statements allowed in this apex code context: ' +  Limits.getLimitScriptStatements());
    
   //This code inefficiently queries the Opportunity object in two seperate queries
    List<Opportunity> opptysClosedLost = [select id, name, closedate, stagename from Opportunity where 
           accountId IN :Trigger.newMap.keySet() and StageName='Closed - Lost'];
   
    System.debug('1.Number of Queries used in this apex code so far: ' + Limits.getQueries());
    System.debug('2.Number of rows queried in this apex code so far: ' + Limits.getDmlRows());
    System.debug('3. Number of script statements used so far : ' +  Limits.getDmlStatements());
    List<Opportunity> opptysClosedWon = [select id, name, closedate, stagename from Opportunity where 
           accountId IN :Trigger.newMap.keySet() and StageName='Closed - Won'];
    
    System.debug('4.Number of Queries used in this apex code so far: ' + Limits.getQueries());
    System.debug('5.Number of rows queried in this apex code so far: ' + Limits.getDmlRows());
    for(Account a : Trigger.new){
    	System.debug('Number of script statements used so far : ' +  Limits.getDmlStatements());
    	//This code inefficiently has two inner FOR loops
    	//Redundantly processes the List of Opportunity Lost
        for(Opportunity o: opptysClosedLost){	
    		if(o.accountid == a.id)
    		   System.debug('Do more logic here...');
        }
        
        System.debug('Number of script statements used so far : ' +  Limits.getDmlStatements());
        //Redundantly processes the List of Opportunity Won
        for(Opportunity o: opptysClosedWon){	
    		if(o.accountid == a.id)
    		   System.debug('Do more logic here...');
        }
    }
 
    System.debug('Final number of script statements used so far : ' +  Limits.getDmlStatements());
    System.debug('Final heap size: ' +  Limits.getHeapSize());
    
}


And here is a sample output after running the trigger by updating an Account record through the user interface. This was generated in a Debug Log.

ApexLimitsOutput.png


The previous example illustrates how value the Limits apex class can be when debugging and analyzing the efficiency of your code. You can let the code output the specific governor limits in runtime and also output throughout the stats of those relevant limits to better understand how the code is leveraging the available resources.

Apex Governor Limit Warning Emails

Additionally, you can enable Apex Governor Limit Warning Emails.

When an end-user invokes an Apex script that surpasses more than 50% of any governor limit, you can specify a user in your organization to receive an email notification of the event with additional details. To enable email warnings:

  1. Log in to Salesforce as an administrator user.
  2. Click Setup | Manage Users | Users.
  3. Click Edit next to the name of the user who should receive the email notifications.
  4. Select the Send Apex Warning Emails option.
  5. Click Save.

Best Practice #8: Use @future appropriately

As articulate throughout this article, it is critical to write your apex code to efficiently handle bulk or many records at a time. This is true for asynchronous apex methods annotated with the @future keyword. Additionally, methods with the @future annotation have the following limits:

  • No more than 10 method calls per Apex invocation
  • No more than 200 method calls per Salesforce license per 24 hours
  • The parameters specified must be primitive dataypes, arrays of primitive datatypes, or collections of primitive datatypes.
  • Methods with the future annotation cannot take sObjects or objects as arguments.
  • Methods with the future annotation cannot be used in Visualforce controllers in either getMethodName or setMethodName methods, nor in the constructor.

In the following example, the Apex trigger invokes an @future method for each Account record it wants to process.

trigger accountAsyncTrigger on Account (after insert, after update) {
  for(Account a: Trigger.new){
   	     //Invoke the @future method for each Account
   	     //This is inefficient and will easily exceed the governor limit of at most 
   	     //10 @future invocation per apex transaction
 		 asyncApex.processAccount((String)a.id);
   }     
} 

And here is the Apex class that defines the @future method

global class asyncApex {

  @future 
  public static void processAccount(Id accountId) {
       List<Contact> contacts = [select id, salutation, firstname, lastname, email 
                from Contact where accountId = :accountId];
 	   
         for(Contact c: contacts){
 		  	  System.debug('Contact Id[' + c.Id + '], FirstName[' + c.firstname + '], LastName[' + c.lastname +']');
 		  	  c.Description=c.salutation + ' ' + c.firstName + ' ' + c.lastname;
        }
        update contacts;        
  }   
}

Since the @future method is invoked within the FOR loop, it will be called N-times (depending on the number of Accounts being processed). And if there are more than 10 accounts, this code will throw an exception for exceeding a governor limit of only 10 @future invocations per apex transaction.

Instead, the @future method should be invoked with a batch of records so it is only invoked once for all records it needs to process.

trigger accountAsyncTrigger on Account (after insert, after update) {
	//By passing the @future method a Set of Ids, it only needs to be
    //invoked once to handle all of the data. 
    asyncApex.processAccount(Trigger.newMap.keySet());
}

And now the @future method is designed to receive a Set of records:

global class asyncApex {
 
  @future 
  public static void processAccount(Set<Id> accountIds) {
       List<Contact> contacts = [select id, salutation, firstname, lastname, email from Contact where accountId IN :accountIds];
 	   for(Contact c: contacts){
 		  	  System.debug('Contact Id[' + c.Id + '], FirstName[' + c.firstname + '], LastName[' + c.lastname +']');
 		  	  c.Description=c.salutation + ' ' + c.firstName + ' ' + c.lastname;
        }
        update contacts;
  }
}

Best Practice #9: Writing Test Methods to verify large datasets

Since Apex code executes in bulk, it is essential to have test scenarios to verify that the Apex being tested is designed to handle large datasets and not just single records To elaborate, an Apex trigger can be invoked either by a data operation from the user interface or by a data operation from the Force.com Web Services API. And the API should send multiple records per batch, leading to the trigger being invoked with several records. Therefore, it is key to have test methods that verify that all Apex code is properly designed to handle larger datasets and that it does not exceed governor limits.

The example below shows you a poorly written trigger that does not handle bulk properly and therefore hits a governor limit. Later, the trigger is revised to properly handle bulk datasets.

First, here is the poorly written Contact trigger. For each contact, the trigger performs a SOQL query to retrieve the related Account. The invalid part of this trigger is that the SOQL query is within the for loop and therefore will throw a governor limit exception if more than 20 Contacts are inserted/updated.

trigger contactTest on Contact (before insert, before update) {
   
   for(Contact ct: Trigger.new){
   	
   	   Account acct = [select id, name from Account where Id=:ct.AccountId];
   	   if(acct.BillingState=='CA'){
   	   	   System.debug('found a contact related to an account in california...');
   	   	   ct.email = 'test_email@testing.com';
   	   	   //Apply more logic here....
   	   }
   }
   
}

Here is the test method that tests if this trigger properly handles volume datasets.

public class sampleTestMethodCls {

	static testMethod void testAccountTrigger(){
		
		//First, prepare 200 contacts for the test data
		Account acct = new Account(name='test account');
		insert acct;
		
		Contact[] contactsToCreate = new Contact[]{};
		for(Integer x=0; x<200;x++){
		    Contact ct = new Contact(AccountId=acct.Id,lastname='test');
		    contactsToCreate.add(ct);
		}
		
		//Now insert data causing an contact trigger to fire. 
		Test.startTest();
		insert contactsToCreate;
		Test.stopTest();	
	}	
}

This test method creates an array of 200 contacts and inserts them. The insert will, in turn, cause the trigger to fire. When this test method is executed, a System.Exception will be thrown when it hits a governor limit. Since the trigger shown above executes a SOQL query for each Contact in the batch, this test method throws the exception 'Too many SOQL queries: 21'. A trigger can only execute at most 20 queries.

Now let's correct the trigger to properly handle bulk operations. The key to fixing this trigger is to get the SOQL query outside the for loop and only do 1 SOQL Query.

trigger contactTest on Contact (before insert, before update) {
   
   Set<Id> accountIds = new Set<Id>();
   for(Contact ct: Trigger.new)
   	   accountIds.add(ct.AccountId);
   
   //Do SOQL Query	   
   Map<Id, Account> accounts = new Map<Id, Account>(
        [select id, name, billingState from Account where id in :accountIds]);
  
   for(Contact ct: Trigger.new){
       if(accounts.get(ct.AccountId).BillingState=='CA'){
   	   	   System.debug('found a contact related to an account in california...');
   	   	   ct.email = 'test_email@testing.com';
   	   	   //Apply more logic here....
   	   }
   } 
   
}

Note how the SOQL query retrieving the accounts is now done once only. If you re-run the test method shown above, it will now execute successfully with no errors and 100% code coverage.

Best Practices #10: Avoid hardcoding Ids

When deploying Apex code between Sandbox and Production, or installing AppExchange packages, it is essential to avoid hardcoding Ids in the compiled Apex code. Therefore, if the record Ids change between environments, the logic can dynamically identify the proper data to operate against and not fail.

In the example, I show how to avoid hardcoding RecordTypeIds.

Here is a sample that hardcodes the record type Ids that are used in an IF/ELSE statement. This will work fine in a specific org that the code is developed in, but if this code were to be installed in a separate org (ie part of an AppExchange package), there is no guarantee that the record type Ids will be the same.

for(Account a: Trigger.new){
     	 
     	  //Error - hardcoded the record type id
     	  if(a.RecordTypeId=='012500000009WAr'){     	  	
     	  	 //do some logic here.....
     	  }else if(a.RecordTypeId=='0123000000095Km'){
     	  	 //do some logic here for a different record type...
     	  }
     	 
     }

Now, to properly handle the dynamic nature of the record type Ids, the following example queries for the record types in the code, stores the dataset in a Map collection for easy retrieval, and ultimately avoid hardcoding the Ids.

//Query for the Account record types
     List<RecordType> rtypes = [Select Name, Id From RecordType 
                  where sObjectType='Account' and isActive=true];
     
     //Create a map between the Record Type Name and Id for easy retrieval
     Map<String,String> accountRecordTypes = new Map<String,String>{};
     for(RecordType rt: rtypes)
        accountRecordTypes.put(rt.Name,rt.Id);
     
      for(Account a: Trigger.new){
     	 
     	  //Use the Map collection to dynamically retrieve the Record Type Id
     	  //Avoid hardcoding Ids in the Apex code
     	  if(a.RecordTypeId==accountRecordTypes.get('Healthcare')){     	  	
     	  	 //do some logic here.....
     	  }else if(a.RecordTypeId==accountRecordTypes.get('High Tech')){
     	  	 //do some logic here for a different record type...
     	  }
     	 
     } 

Summary

This article covered many of the core Apex coding best practices. These principles should be incorporated into your Apex code in order to write efficient, scalable code. We discussed how to bulkify your code by handling all incoming records instead of just one. We also illustrated how to avoid having SOQL queries inside a FOR loop to avoid governor limits. Additionally, there are examples of how to output helpful governor limit debugging statements. Along with several other best practices. By following these principles, you are on a great path for success with Apex code.

References

About the Author

Andrew Albert is a Technical Evangelist at salesforce.com, focusing on the Force.com Platform. I work with ISVs and developers that are looking to build applications on the Force.com platform.