Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Mastering Apex Programming

You're reading from   Mastering Apex Programming A Salesforce developer's guide to learn advanced techniques and programming best practices for building robust and scalable enterprise-grade applications

Arrow left icon
Product type Paperback
Published in Nov 2023
Publisher Packt
ISBN-13 9781837638352
Length 394 pages
Edition 2nd Edition
Languages
Concepts
Arrow right icon
Author (1):
Arrow left icon
Paul Battisson Paul Battisson
Author Profile Icon Paul Battisson
Paul Battisson
Arrow right icon
View More author details
Toc

Table of Contents (28) Chapters Close

Preface 1. Section 1: Triggers, Testing, and Security
2. Chapter 1: Common Apex Mistakes FREE CHAPTER 3. Chapter 2: Debugging Apex 4. Chapter 3: Triggers and Managing Trigger Execution 5. Chapter 4: Exceptions and Exception Handling 6. Chapter 5: Testing Apex Code 7. Chapter 6: Secure Apex Programming 8. Section 2: Asynchronous Apex
9. Chapter 7: Utilizing Future Methods 10. Chapter 8: Working with Batch Apex 11. Chapter 9: Working with Queueable Apex 12. Chapter 10: Scheduling Apex Jobs 13. Section 3: Integrations
14. Chapter 11: Integrating with Salesforce 15. Chapter 12: Using Platform Events 16. Chapter 13: Apex and Flow 17. Chapter 14: Apex REST and Custom Web Services 18. Chapter 15: Outbound Integrations – REST 19. Chapter 16: Outbound Integrations – SOAP 20. Chapter 17: DataWeave in Apex 21. Section 4: Apex Performance
22. Chapter 18: Performance and the Salesforce Governor Limits 23. Chapter 19: Performance Profiling 24. Chapter 20: Improving Apex Performance 25. Chapter 21: Performance and Application Architectures 26. Index 27. Other Books You May Enjoy

Null pointer exceptions

Almost every developer working with the Salesforce platform will have 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 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 behind the scenes.

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 of a Person class called paul, 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 instances within Apex occur: when working with specific object instances and when referencing values within 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 we now attempt to run System.debug(paul.name);, we will get an exception of type NullPointerException 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 a 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 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 for any 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 an individual’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 value 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 guard clause, we have proactively filtered out any bad keys for the map by removing the error.

As an alternative, if we were looping 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 on 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 instance. In these cases, our if statement is one solution. We can also use a feature called safe navigation to allow Apex to assist us in avoiding these issues.

Safe navigation operator

The discussion and methods shown in the preceding sections provide ways for us to code to avoid a NullPointerException instance occurring through the way we structure our code, which can also assist in performance. For example, the second set of code for looping through a pre-filtered list of IDs based on the map’s keys will avoid unnecessary looping and operations. Since the first edition of this book was published, Salesforce has added in a safe navigation operator that helps remove a lot of the null checks we previously had to perform.

The safe navigation operator (?.) will return null if a value is unavailable (when previously a NullPointerException instance would occur) and return a value if one is available. We can rewrite our badge ID retrieval code as follows:

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

In this instance, if badgeId is a key on the map and can be retrieved, then the FirstName value for the contact is assigned to ownerName. If the value of badgeId is not a valid key, then ownerName is set to null. We can see some more examples in the following code block:

Integer employeeCount = myAccount?.NumberOfEmployees; //returns null if myAccount is not an Account instance
paul?.getContactRecord()?.FirstName; //returns null if paul is null or
  the getContactRecord() method returns null
String accName = [SELECT Name FROM Account WHERE Account_Reference__c 
  = :externalAccountKey]?.Name; //returns null if no Account record is
    found by the query

The safe navigation operator is an extremely useful operator to help developers in minimizing errors; however, it should not be considered a panacea. You may unintentionally cause further NullPointerException instances within your code by not correctly verifying whether a null value has been returned. In the following example, we take our badge ID code to retrieve the name of the badge’s owner:

String ownerName = contactsByBadgeId.get(badBadgeId)?.FirstName;

We may then use this value within a component or page to display a greeting to the individual. If a null value has been returned, this can lead to some unexpected messages:

String ownerName = contactsByBadgeId.get(badBadgeId)?.FirstName;
String welcomeMessage = 'Hello there ' + ownerName;
//"Hello there null" would be displayed as a greeting.

It is important, therefore, that within your code base and among your development team an agreement is reached as to where the responsibility for these checks lies within the code to ensure that unintended consequences do not occur.

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. We can also use the safe navigation operator in instances where we want to ensure safety to allow the code execution to continue, but must then be aware of checking for null values within our code. With this in mind, let us now look at how we can effectively bulkify our Apex code.

You have been reading a chapter from
Mastering Apex Programming - Second Edition
Published in: Nov 2023
Publisher: Packt
ISBN-13: 9781837638352
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €18.99/month. Cancel anytime