Book Image

Mastering Apex Programming

By : Paul Battisson
5 (1)
Book Image

Mastering Apex Programming

5 (1)
By: Paul Battisson

Overview of this book

As applications built on the Salesforce platform are now a key part of many organizations, developers are shifting focus to Apex, Salesforce’s proprietary programming language. As a Salesforce developer, it is important to understand the range of tools at your disposal, how and when to use them, and best practices for working with Apex. Mastering Apex Programming will help you explore the advanced features of Apex programming and guide you in delivering robust solutions that scale. This book starts by taking you through common Apex mistakes, debugging, exception handling, and testing. You'll then discover different asynchronous Apex programming options and develop custom Apex REST web services. The book shows you how to define and utilize Batch Apex, Queueable Apex, and Scheduled Apex using common scenarios before teaching you how to define, publish, and consume platform events and RESTful endpoints with Apex. Finally, you'll learn how to profile and improve the performance of your Apex application, including architecture trade-offs. With code examples used to facilitate discussion throughout, by the end of the book, you'll have developed the skills needed to build robust and scalable applications in Apex.
Table of Contents (21 chapters)
1
Section 1 – Triggers, Testing, and Security
8
Section 2 – Asynchronous Apex and Apex REST
15
Section 3 – Apex Performance

Null pointer exceptions

Almost every developer working with the Salesforce platform has encountered the dreaded phrase Attempt to de-reference a null object. At its heart, this is one of the simplest errors to both generate and handle effectively, but its error message can cause great confusion for new and experienced developers alike, as it is often unclear how the exception is occurring.

Let's start by discussing in the abstract form how the error is generated. This is definitively a runtime error, caused by the system attempting to read data from memory where the memory is blank. Apex is built on top of the Java language and uses the Java Virtual Machine (JVM) runtime under the hood. What follows is a highly simplified discussion of how Java manages memory, which will help us to understand what is happening under the hood.

Whenever an object is instantiated in Java, it is created and managed on the heap, which is a block of memory used to dynamically hold data for objects and classes at runtime. A separate set of memory, called the stack, stores references to these objects and instances. So, in simplistic terms, when you instantiate an instance called paul of a Person class, that instance is stored on the heap and a reference to this heap memory is stored on the stack, with the label paul. Apex is built on Java and compiles down to Java bytecode (this started after an update from Salesforce in 2012), and, although Apex does not utilize a full version of the JVM, it uses the JVM as the basis for its operations, including garbage and memory management.

With this in mind, we are now better able to understand how the two most common types of NullPointerException within Apex occur: when working with specific object instances and when referencing values on maps.

Exceptions on object instances

Let's imagine I have the following code within my environment:

public class Person {
	public String name;
}
Person paul;

In this code, we have a Person class defined, with a single publicly accessible member variable. We have then declared a variable, paul, using this new data type. In memory, Salesforce now has a label on the stack called paul that is not pointing to any address on the heap, as paul currently has the value of null.

If I now attempt to run System.debug(paul.name);, we will get a NullPointerException exception with the message Attempt to de-reference a null object. What is happening is that the system is trying to use the paul variable to retrieve the object instance and then access the name property of that instance. Because the instance is null, the reference to this memory does not exist, and so NullPointerException is thrown; that is, we have nothing to point with.

With this understanding of how memory management is working under the hood (in an approximate fashion) and how we are generating these errors, it is therefore pretty easy to see how we code against them—avoid calling methods and accessing variables and properties on an object that has not been instantiated. This can be done by ensuring we always call a constructor when initializing a variable, as shown in the following code snippet:

Person paul = new Person();

In general, when developing, we should pay attention to any public methods or variables that return complex types or data from a complex type. A common practice is simply to instantiate new instances of the underlying object in the constructor of data that may be returned before being populated.

Exceptions when working with maps

Another common way in which this exception presents itself is when working with collections and data retrieved from collections—most notably, maps. As an example, we may have some data in a map for us to use in processing unrelated records. Let's say we have a Map<String, Contact> contactsByBadgeId instance that allows us to use a contact's unique badge ID string to retrieve their contact record for processing. Let's try to run the following:

String badBadgeId = 'THIS ID DOES NOT EXIST';
String ownerName = contactsByBadgeId.get(badBadgeId).FirstName;

Assuming that the map will not have the key that badBadgeId is holding, the get method on the map will return null and our attempt to access the FirstName property will be met with NullPointerException being thrown.

The simplest and most effective way to manage this is to wrap our method in a simple if block, as follows:

String badBadgeId = 'THIS ID DOES NOT EXIST';
if(contactsByBadgeId.containsKey(badBadgeId)) {
    String ownerName = contactsByBadgeId.get(badBadgeId).    FirstName;
}

By adding this wrapper, we have proactively filtered out any bad keys for the map by removing the error.

As an alternative, we could loop through a list of badge IDs, like this:

for(String badgeId : badgeIdList) {
    String ownerName = contactsByBadgeId.get(badBadgeId).    FirstName;
}

We could also use the methods available to set collections to both potentially reduce our loop size and avoid the issue, as follows:

Set<String> badgeIdSet = new Set<String>(badgeIdList).retainAll(contactsByBadgeId.keySet());
for(String badgeId : badgeIdSet) {
    String ownerName = contactsByBadgeId.get(badBadgeId).    FirstName;
}

In the preceding example, we have filtered down the items to be iterated through to only those in the keySet instance of the map. This may not be possible in many instances, as we may be looping through a collection of a non-primitive type or a type that does not match our keySet. In these cases, our if statement is the best solution.

In general, most NullPointerException instances occur when a premature assumption about the availability of data has been made—for example, that the object has been instantiated or that our map contains the key we are looking for. Trying to recognize these assumptions will assist in avoiding these exceptions, going forward. With this in mind, let's now look at how we can effectively bulkify our Apex code.

Winter' 21 Safe Navigation Operator

In Winter 21, which will be released after this book is published, Salesforce will introduce the safe navigation operator to help avoid NullPointerException problems. This operator will allow you to write code such as contactsByBadgeId.get(badBadgeId)?.FirstName, which will return Null if badBadgeId is not in the map, avoiding NullPointerException. This will be an extremely useful method that you should consider using as part of your practice to help avoid NullPointerException errors.

safe navigation operator

NullPointerException

contactsByBadgeId.get(badBadgeId)?.FirstName

Null

badBadgeId

NullPointerException

NullPointerException