Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Mastering Apex Programming
Mastering Apex Programming

Mastering Apex Programming: A developer's guide to learning advanced techniques and best practices for building robust Salesforce applications

eBook
$28.99 $41.99
Paperback
$51.99
Subscription
Free Trial
Renews at $19.99p/m

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
OR
Modal Close icon
Payment Processing...
tick Completed

Billing Address

Table of content icon View table of contents Preview book icon Preview Book

Mastering Apex Programming

Chapter 1: Common Apex Mistakes

In this chapter, we will cover common mistakes made when developing in Apex and the appropriate defensive programming techniques to avoid them. Before we begin to move into more advanced topics within the Apex language, it is important that we first of all ensure a common grounding in removing any common errors within our code. For some of you, this material may simply be a refresher or a reiteration of some practices; however, these mistakes form the basis of many of the common exceptions and errors that I have seen in my time working with the Salesforce platform. By the end of this chapter, you will hopefully be more aware of when these mistakes may occur within your code and be able to develop in a defensive style to avoid them.

In this chapter, we will cover the following topics:

  • Null pointer exceptions
  • Bulkification of Apex code to avoid Governor Limits
  • Hardcoding references to specific object instances
  • Patterns to deal with managing data across a transaction

Technical Requirements

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

Retrieving configuration data in a bulkified way

No book on Apex is complete without some discussion around bulkification. Bulkification is a requirement in Salesforce due to the Governor Limits that are imposed on developers because of the multi-tenant nature of the platform. Many developers that are new to the platform see the Governor Limits as a hindrance rather than an assistance. We will cover this in more detail in Chapter 13, Performance and the Salesforce Governor Limits. However, a common mistake that developers make on the platform is to not bulkify their code appropriately—particularly, triggers. It is also common for intermediate to advanced developers to not bulkify their non-trigger code appropriately either. We will discuss bulkification of triggers more explicitly in Chapter 3, Triggers and Managing Trigger Execution, and will cover querying and Data Manipulation Language (DML) within loops later in this chapter. Firstly, however, I want to discuss bulkifying the retrieval of data that is not typically stored in a custom or standard object—configuration data.

Hot and cold data

I want to begin this section with a discussion on hot and cold data within the system, as well as the implications this has on bulkification. For all of the data within our system, let's assume that the data starts off with a temperature of 0 (our scale should not matter, but let's assume we are using °C, where 0 is freezing and 100 is boiling). Every time that our data is written to, its temperature increases by one degree, and if an entire day goes without it being updated, it drops a degree and decreases by one. If we were to run this thought experiment across our data, we would then obtain a scale for each data type we are retrieving, where the data would range from very cold (that is, hardly ever written to) through to extremely hot (edited multiple times a day).

For most objects in Salesforce, the temperature graph of the data would appear in a long-tailed distribution manner—that is, an initial peak of activity as a record is created and then updated, until it reaches a stage in its life cycle where it is no longer viewed and only really included for auditing and reporting purposes. Think of an opportunity record, for example: a lot of initial activity until it is closed, won, or lost, and then it is used mainly for reporting. When working with these records in Apex, we will need to ensure we query for them to get the latest version for accurate and up-to-date data. As we are in most cases not writing Apex to work on "cold" instances of this data, we need to be aware of the fact that these records may change during the scope of a transaction due to an update we are making. Our normal bulkification practices, discussed next, will help us manage this.

What about data that is truly cold—that is, created for all intents and purposes but never updated? Think of a custom metadata record that holds some configuration used in an Apex process. Such information is created and then only updated when the process itself changes. For such data, we actively want to avoid querying multiple times during a transaction yet ensure that it can be made available across the entire transaction as needed. As Salesforce applications have grown and more custom configuration has been added to make the applications more dynamic and easier to update, more organizations have deployed custom metadata, custom settings, and custom configuration objects (although these are now largely superseded by custom metadata and custom settings). How do we as developers manage retrieval of this data in a manner that is bulkified and that allows us to reuse this data across a transaction?

Retrieving and sharing data throughout a transaction

For this use case, a developer can either use the singleton pattern or make appropriate use of static variables to manage the retrieval of this data. We could implement a singleton utility class, as follows:

public class ExampleSingleton {
	
    private static ExampleSingleton instance;
    private Example__mdt metadata;
    
    public static ExampleSingleton getInstance() {
        if(instance == null) {
            instance = new ExampleSingleton();
        }
        return instance;
    }
    
    private ExampleSingleton() {
    }
    
    public Example__mdt getMetadata() {
        if(metadata == null) {
            metadata = [SELECT Id FROM Example__mdt LIMIT 1];
        }
        return metadata;
    }
    
}

Our private static member variable—instance—holds the unique instance of the ExampleSingleton class for the transaction. The getInstance method is the only way of either instantiating or retrieving the instance for use, and we have ensured this by making the default constructor private. We can use this instance to retrieve our Example__mdt record, using the getMetadata method on our returned instance. The actual instance of the Example__mdt record is stored as a private member for the class and is state abstracted away from the change.

The benefit of such an approach is that we can both encapsulate our data and its workings and ensure that we are only ever retrieving the information from the database once. This singleton could be used to hold many different types of data as needed so that the entire transaction can scale in its usage of such data in a common way.

Alternatively, we could implement a static class such as the following one:

public class ExampleStatic {
    private static Example__mdt metadata;
    
    public static Example__mdt getExampleMetadata() {
        if(metadata == null) {
            metadata = [SELECT Id FROM Example__mdt LIMIT 1];
        }
        return metadata;
    }
    private ExampleStatic() {
    }
}

Again, we have ensured that our metadata will only be loaded once across the transaction and have a smaller code footprint to manage. Note that this is not a true static class, as these are not available in Apex. However, there is a close enough analogy to this in the language that we can consider as a static class (it cannot be externally instantiated due to its private constructor).

For cold data such as custom metadata, custom settings, or any custom configuration in objects, the use of a singleton or a static class can greatly improve bulkification. I have seen instances in production where the same set of metadata records was retrieved multiple times during a transaction as the code began to interact by recursively firing triggers through a combination of updates.

Singleton versus static class

It is a fair question to ask right now whether a singleton or a static class instance should be used in utility classes. The answer (as with all good questions) is it depends. While both have similar functionality from our perspective in terms of retrieving data only once, singletons can be passed around as object instances for use across the application. They can also extend other classes and implement interfaces with the full range of Apex's object-oriented (OO) features. While static classes cannot do this, they are more useful in lightweight implementations where the OO features of the language are not required.

Bulkification – querying within loops

Another common mistake seen in Apex is querying for data within loops. This is different than repeated querying for data, as discussed previously, but instead it focuses on performing a query with a (potentially) unique outcome for each iteration of a loop. This is particularly true within triggers.

When working with a trigger, you should always prepare your code to handle a batch of 200 records at once. This is true regardless of whether or not you believe the tool will only pass records to the trigger individually; all that is required is for an enterprising administrator to create a flow that manipulates multiple records that fire your trigger, and you will have issues.

Consider the following code block, wherein we are looping through each contact we have been provided in a contact trigger and retrieving the related account record, including some information:

trigger ContactTrigger on Contact (before insert, after insert) {
    switch on Trigger.operationType {
        when BEFORE_INSERT {
			for(Contact con : Trigger.new) {
                  Account acc = [SELECT UpsellOpportunity__c                    FROM Account WHERE Id = :con.AccountId];
                      con.Contact_for_Upsell__c = acc.                      UpsellOpportunity__c != 'No';
		    }
		} 
		when AFTER_INSERT {
			//after insert code
		}
	}
}

This simple trigger will set the Contact_for_Upsell__c field to true if the account is marked as having any upsell opportunity.

There are a couple of fairly obvious problems with the way we are querying here. Firstly, this is not bulkified—if we have 200 records passed into the trigger (over 100 records, in fact), we will break the governor limit for Salesforce Object Query Language (SOQL) queries and receive an exception that we cannot handle. Secondly, this setup is also inefficient as it may retrieve the same account record from the database twice.

A better way to manage this would be to gather all of the account IDs in a set and then query once. Not only will this avoid the governor limit—it will also avoid us querying for duplicate results. An updated version of the code to do this is shown here:

trigger ContactTrigger on Contact (before insert, after insert) {
	switch on Trigger.operationType {
        when BEFORE_INSERT {
    
		    Set<Id> accountIds = new Set<Id>();
		    
		    for(Contact con : Trigger.new) {
				accountIds.add(con.AccountId);
		    }
		    
              Map<Id, Account> accountMap = new Map<Id,               Account>([SELECT UpsellOpportunity__c                FROM Account WHERE Id in :accountIds]);
		    
			for(Contact con : Trigger.new) {
                      con.Contact_for_Upsell__c = accountMap.                      get(con.AccountId).UpsellOpportunity__c                       != 'No';
		    }    
		}
		when AFTER_INSERT {
			//after insert code
		}
	}
}

In this code, we declare a Set<Id> called accountIds to hold the account ID for each contact without duplicates. We then query our accounts into a Map<Id, Account> so that when looping through each contact for a second time we can set the value correctly.

Some of you may now be wondering if we have merely moved our performance issue from having too many queries to having multiple loops through all the data. In Chapter 14, Performance Profiling, when we talk about performance profiling, we will cover the use of big-O notation in detail when discussing scaling. However, to touch on the subject of scaling here, it is worth doing some rudimentary analysis. Looping through these records (maximum 200) will be extremely quick on the central processing unit (CPU) and is an inexpensive operation. It is also an operation that scales linearly as the number of records within the trigger grows. In our original trigger, for each new record we had the following:

  • One loop iteration
  • One query

This is scaled linearly at a rate of 1x for both resources—that is, doubling the items doubled the resources being utilized, until a point of failure with a governor limit (in this instance, queries). In our new trigger structure, we have the following for each record:

  • Two loop iterations (one for each for loop)
  • Zero additional queries

Our new resource usage scales linearly for loop iterations but is constant for queries, which are a more limited resource. As we will see later, this is the type of optimization we want within our code. It is therefore imperative that whenever we are looping through records and wish to query related data, we do so in a bulkified manner that, wherever possible, performs a single query for the entire loop.

Bulkification – DML within loops

Similar to the issue of querying in loops is that of performing DML within loops. The limit for DML statements is higher than that of SOQL queries at the time of writing and so is unlikely to present itself as early; however, it follows the same root cause and also the solution.

Take the following code example, in which we are now in the after insert context for our trigger:

trigger ContactTrigger on Contact (before insert) {
	switch on Trigger.operationType {
        when BEFORE_INSERT {
    		//previous trigger code  
		}
		when AFTER_INSERT {
			for(Contact con : Trigger.new) {
                if(con.Contact_for_Upsell__c) {
                    Task t = new Task();
                    t.Subject = 'Discuss opportunities with new                     contact';
                    t.OwnerId = con.OwnerId;
                    t.WhoId = con.Id;
                    insert t;
                }
		    } 
		}
	}
}

Here, we are creating a task for the owner of any new contact that is marked for upsell to contact them and discuss potential opportunities. In our worst-case bulk scenario here, we have 200 contacts that all have the Contact_for_Upsell__c checkbox checked. This will lead to each iteration firing a DML statement that will cause a governor limit exception on record 151. Again, using our rudimentary analysis, we can see that for each additional record on the trigger, we have an additional DML statement that scales linearly until we breach our limit.

Instead, whenever making DML statements (particularly in triggers), we should ensure that we are using the bulk format and passing lists of records to be manipulated into the statement. For example, the trigger code should be written as follows:

trigger ContactTrigger on Contact (before insert) {
	switch on Trigger.operationType {
        when BEFORE_INSERT {
    		//previous trigger code  
		}
		when AFTER_INSERT {
                List<Task> tasks = new List<Task>();
                for(Contact con : Trigger.new) {
                  if(con.Contact_for_Upsell__c) {
                    Task t = new Task();
                    t.Subject = 'Discuss opportunities with new                     contact';
                    t.OwnerId = con.OwnerId;
                    t.WhoId = con.Id;
                    tasks.add(t);
                }
		    }
              insert tasks;
		}
	}
}

This new code has a constant usage of DML statements, one for the entire operation, and can happily scale up to 200 records.

Hardcoding

The final common mistake I want to discuss here is that of hardcoding within Apex—particularly, hardcoding any type of unique identifier such as an ID or a name. For IDs, it is probably quite obvious for most developers as it is well established that between different environments, including sandbox and production environments, IDs can and should differ. If you are creating a sandbox from a production environment, then at the time of creation, the IDs are synchronized for any data that is copied down to the sandbox environment. Following this, IDs do not remain synchronized between the two and are generated when a record is created within that environment.

Despite this, many developers, particularly those working within a single environment such as consultants or in-house developers, will hardcode certain IDs if needed. For example, consider the following code:

for(Account acc : Trigger.new) {
	if(acc.OwnerId = 'SOME_USER_ID') {
		break;
	}
	//do something otherwise
}

This code is designed to skip updates on account records within our trigger context owned by a particular user, most commonly an application programming interface (API) user or an integration user. This pattern enables a developer to filter these records out so that if an update is coming via an integration, actions are skipped and the integration can update records unimpeded.

Should this user ID change, then we will get an error or issue here, so it is wise to remove this hardcoded value. Given that the ID for the user should be copied from production to any sandboxes, you may ask why this is needed. Firstly, there is no guarantee that the user will not be changed for the integration, going forward. Secondly, for development purposes, when initially writing this code, the user will not likely exist in the production organization (org), and so, in your first deployment, you will have to tweak the code to be environment-specific. Thirdly, this also limits your ability to test the code effectively, going forward. We will see how shortly.

As an update to this code, some may recommend making the following change (note that this is precisely the instance where we would extract this query to a utility class; however, we are inlining the query here for ease of display and reading):

for(Account acc : Trigger.new) {
    User apiUser = [SELECT Id FROM User WHERE Username = 'my.    [email protected]'];
    if(acc.OwnerId = apiuser.Id) {
		break;
    }
    //do something otherwise
}

This code improves upon our previous code in that we are no longer hardcoding the user record ID, although we are still hardcoding the name. Again, should this change over time, then we should still have an issue, such as when we are working in a sandbox environment and the sandbox name is appended to the username. In this instance, the code would not be executable, including in a test context, without updating the record to be correct. This could be done manually every time a new sandbox is created, or through the code in the test. This means that our test is now bound to this user, which is not a good practice for tests, should the user change again.

In this instance, we should remove the name string to a custom setting for flexibility and improved testability. If we were to define a custom setting that held the value (for example, Integration_Settings__c), then we could easily retrieve the custom setting and the username for a query at runtime without the query, as follows:

for(Account acc : Trigger.new) {
    Integration_Settings__c setting = Integration_Settings__c.    getInstance();
    User apiUser = [SELECT Id FROM User WHERE Username =     setting.Api_Username__c];
    if(acc.OwnerId = apiuser.Id) {
		break;
    }
    //do something otherwise
}

Such a pattern allows us many benefits, as follows:

  • Firstly, we can now apply different settings across the org and profiles, should we so desire. This can be increasingly useful in orgs where multiple business units operate.
  • Secondly, for testing, we can create a user within our test data and assign their username to the setting for the test within Apex. This allows our tests to run independently of the actual org configuration and makes processes such as continuous integration (CI) simpler.
  • Finally, we could extract both of these lines (the retrieval of the custom setting and the user query) to a utility class to abstract away, for the entire transaction to use as needed.

Summary

In this chapter, we reviewed some of the common Apex mistakes made by developers and how to resolve them. For many of you, the topics presented within this chapter will be familiar, although are hopefully a worthwhile refresher, with maybe some additional understanding or thoughts.

We begin this book with this chapter, as it is imperative we consider how to remove these common mistakes before we look at how to extend our knowledge around the rest of the platform's features. We also tried to cover in greater detail than is typical the reasoning behind some of these errors, either from the perspective of the underlying machine, as with the NullPointerException discussion we started with, or via the impact upon developer and deployment productivity, such as our final discussion on hardcoding.

To master any language means beginning by removing the minor common niggles that can cause issues and easily resolving bugs. Hopefully, through having a deeper or broader understanding of these issues and how they arise, you can more readily spot and rectify them in advance. That is not to say you will stop making them: I find these bugs and issues in my own code regularly, but I am able to recognize them in advance as I begin to develop, to stop them as routine and habit take hold.

Now that we have discussed these common problems and how to resolve them, we will move on to move detailed and prescriptive debugging in the next chapter.

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Discover how to build reliable applications with Apex by avoiding common mistakes and pitfalls
  • Learn how to use the different asynchronous programming tools in Apex
  • Profile and improve the performance of your Apex code

Description

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.

Who is this book for?

This book is for Salesforce developers who are interested in mastering Apex programming skills. You’ll also find this book helpful if you’re an experienced Java or C# developer looking to switch to Apex programming for developing apps on the Salesforce platform. Basic Apex programming knowledge is essential to understand the concepts covered.

What you will learn

  • Understand common coding mistakes in Apex and how to avoid them using best practices
  • Find out how to debug a Salesforce Apex application effectively
  • Explore different asynchronous Apex options and their common use cases
  • Discover tips to work effectively with platform events
  • Develop custom Apex REST services to allow inbound integrations
  • Build complex logic and processes on the Salesforce platform

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : Nov 20, 2020
Length: 368 pages
Edition : 1st
Language : English
ISBN-13 : 9781800204331
Vendor :
Salesforce
Languages :
Tools :

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
OR
Modal Close icon
Payment Processing...
tick Completed

Billing Address

Product Details

Publication date : Nov 20, 2020
Length: 368 pages
Edition : 1st
Language : English
ISBN-13 : 9781800204331
Vendor :
Salesforce
Languages :
Tools :

Packt Subscriptions

See our plans and pricing
Modal Close icon
$19.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
$199.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just $5 each
Feature tick icon Exclusive print discounts
$279.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just $5 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total $ 155.97
Mastering Apex Programming
$51.99
Salesforce Lightning Platform Enterprise Architecture
$54.99
Apex Design Patterns
$48.99
Total $ 155.97 Stars icon
Banner background image

Table of Contents

20 Chapters
Section 1 – Triggers, Testing, and Security Chevron down icon Chevron up icon
Chapter 1: Common Apex Mistakes Chevron down icon Chevron up icon
Chapter 2: Debugging Apex Chevron down icon Chevron up icon
Chapter 3: Triggers and Managing Trigger Execution Chevron down icon Chevron up icon
Chapter 4: Exceptions and Exception Handling Chevron down icon Chevron up icon
Chapter 5: Testing Apex Code Chevron down icon Chevron up icon
Chapter 6: Secure Apex Programming Chevron down icon Chevron up icon
Section 2 – Asynchronous Apex and Apex REST Chevron down icon Chevron up icon
Chapter 7: Utilizing Future Methods Chevron down icon Chevron up icon
Chapter 8: Working with Batch Apex Chevron down icon Chevron up icon
Chapter 9: Working with Queueable Apex Chevron down icon Chevron up icon
Chapter 10: Scheduling Apex Jobs Chevron down icon Chevron up icon
Chapter 11: Using Platform Events Chevron down icon Chevron up icon
Chapter 12: Apex REST and Custom Web Services Chevron down icon Chevron up icon
Section 3 – Apex Performance Chevron down icon Chevron up icon
Chapter 13: Performance and the Salesforce Governor Limits Chevron down icon Chevron up icon
Chapter 14: Performance Profiling Chevron down icon Chevron up icon
Chapter 15: Improving Apex Performance Chevron down icon Chevron up icon
Chapter 16: Performance and Application Architectures Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon

Customer reviews

Top Reviews
Rating distribution
Full star icon Full star icon Full star icon Full star icon Half star icon 4.9
(12 Ratings)
5 star 91.7%
4 star 8.3%
3 star 0%
2 star 0%
1 star 0%
Filter icon Filter
Top Reviews

Filter reviews by




Ram Sep 27, 2023
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Best book on Apex.
Subscriber review Packt
CathyW Jan 01, 2021
Full star icon Full star icon Full star icon Full star icon Full star icon 5
This book is intended for intermediate and advanced Apex developers, and I consider myself to be an “advanced beginner”. However the concepts are explained very clearly, so I was able to understand and learn functionality that I haven’t used before.I found the chapter on common mistakes, and the section on performance to be particularly interesting and very helpful. The code examples included in each chapter helped to illustrate key points, and the sidebar notes in the text that referenced websites to learn topics in more depth were quite useful.I expect to refer to this book frequently, and I highly recommend it to any Apex developer who would like to learn new techniques and improve their code, no matter what their level of skill.My copy of the book was courtesy of Packt publishing.
Amazon Verified review Amazon
AMol SOlanki Jan 19, 2021
Full star icon Full star icon Full star icon Full star icon Full star icon 5
I am an experienced software developer who has 8+ years of experience. I have read a lot of books, teaching various programming languages. I think this is the best programming book I have ever read. It provides detailed information on almost every aspect of the apex. It provides reasonable and persuasive explanations for the reasons why Salesforce Apex is designed in the way now, rather than just explaining the concept. In this way, it makes understanding Apex much easier.In addition, almost all examples in this book are short, straightforward and easy to understand. They are simply used to help explain the concepts and programming skills being taught, rather than to focus too much on being practical. This is another excellent aspect of the book.This book is for advance developer who wants to understand, what goes beneath the apex execution engine and how a proper code should be written keeping the governor limits in mind.
Amazon Verified review Amazon
JR Detroit Feb 10, 2021
Full star icon Full star icon Full star icon Full star icon Full star icon 5
I have been a longtime fan of Paul Battisson, finding many of his blog posts very valuable over the years.I consider myself an experienced software developer with over 25 years of experience. I have been in the Salesforce ecosystem for over 15 years. I am a bit of a book nut, and much to the dismay of my family, I buy and read a lot of Salesforce books.This book is a great combination of educational and reference materials. There are examples both of the theory, and the practice covered in each concept of the book, which I find very valuable for a deeper understanding.The “Common Mistakes” chapter was a great way to start the book, and any Apex developer regardless of their experience level will have run into at least one of the items covered, and probably, all of them.In all of the chapters, the code samples provide great support for helping any developer “practice” the concepts being presented.My suggestions for the “Second Edition” (please don’t take these as negatives), would be to add the concept of an event-driven logging pattern that leverages platform events. In my personal experience in implementing a logging architecture, frequently, a failed DML operation or an unhandled exception will cause the Logging records to get rolled back, just like the primary transaction data.And, for me personally, an area that developers that I work with struggle with is code quality. More recognition of the PMD Static Code analysis tools would be valuable. I would suggest, even mentioning the specific rules that might be addressed with a particular pattern in the topic chapters themselves.This book will definitely part of my reference library, and I would recommend any serious Apex developer consider adding it to theirs.
Amazon Verified review Amazon
Meighan Mar 02, 2021
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Great book! Loved it for learning apex and getting better knowledge of coding server side. It was a great book that was an awesome deep dive into coding for Salesforce, covering everything you'd want to know to become an 'Apex Master'. Not a chaper went by that I didn't learn at least one new thing. Highly recommend this book for Salesforce developers and for Architects, new to coding and those who have been writing apex for years.
Amazon Verified review Amazon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

How do I buy and download an eBook? Chevron down icon Chevron up icon

Where there is an eBook version of a title available, you can buy it from the book details for that title. Add either the standalone eBook or the eBook and print book bundle to your shopping cart. Your eBook will show in your cart as a product on its own. After completing checkout and payment in the normal way, you will receive your receipt on the screen containing a link to a personalised PDF download file. This link will remain active for 30 days. You can download backup copies of the file by logging in to your account at any time.

If you already have Adobe reader installed, then clicking on the link will download and open the PDF file directly. If you don't, then save the PDF file on your machine and download the Reader to view it.

Please Note: Packt eBooks are non-returnable and non-refundable.

Packt eBook and Licensing When you buy an eBook from Packt Publishing, completing your purchase means you accept the terms of our licence agreement. Please read the full text of the agreement. In it we have tried to balance the need for the ebook to be usable for you the reader with our needs to protect the rights of us as Publishers and of our authors. In summary, the agreement says:

  • You may make copies of your eBook for your own use onto any machine
  • You may not pass copies of the eBook on to anyone else
How can I make a purchase on your website? Chevron down icon Chevron up icon

If you want to purchase a video course, eBook or Bundle (Print+eBook) please follow below steps:

  1. Register on our website using your email address and the password.
  2. Search for the title by name or ISBN using the search option.
  3. Select the title you want to purchase.
  4. Choose the format you wish to purchase the title in; if you order the Print Book, you get a free eBook copy of the same title. 
  5. Proceed with the checkout process (payment to be made using Credit Card, Debit Cart, or PayPal)
Where can I access support around an eBook? Chevron down icon Chevron up icon
  • If you experience a problem with using or installing Adobe Reader, the contact Adobe directly.
  • To view the errata for the book, see www.packtpub.com/support and view the pages for the title you have.
  • To view your account details or to download a new copy of the book go to www.packtpub.com/account
  • To contact us directly if a problem is not resolved, use www.packtpub.com/contact-us
What eBook formats do Packt support? Chevron down icon Chevron up icon

Our eBooks are currently available in a variety of formats such as PDF and ePubs. In the future, this may well change with trends and development in technology, but please note that our PDFs are not Adobe eBook Reader format, which has greater restrictions on security.

You will need to use Adobe Reader v9 or later in order to read Packt's PDF eBooks.

What are the benefits of eBooks? Chevron down icon Chevron up icon
  • You can get the information you need immediately
  • You can easily take them with you on a laptop
  • You can download them an unlimited number of times
  • You can print them out
  • They are copy-paste enabled
  • They are searchable
  • There is no password protection
  • They are lower price than print
  • They save resources and space
What is an eBook? Chevron down icon Chevron up icon

Packt eBooks are a complete electronic version of the print edition, available in PDF and ePub formats. Every piece of content down to the page numbering is the same. Because we save the costs of printing and shipping the book to you, we are able to offer eBooks at a lower cost than print editions.

When you have purchased an eBook, simply login to your account and click on the link in Your Download Area. We recommend you saving the file to your hard drive before opening it.

For optimal viewing of our eBooks, we recommend you download and install the free Adobe Reader version 9.