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