Welcome to another installment in our discussion of Apex errors. As we decided in a recent post, errors are everywhere, they’re a big problem, and we need to address them. Let’s have a chat today about the varieties of Apex errors and the options developers have for resolving them.
First up: security – the most important category of errors for many Salesforce customers.
Security Errors
Perhaps the most important family of errors has to do with security – making sure access to sensitive information is restricted and secure. There are several security-related aspects of Apex code.
Record Access
You’ll want to make sure the logged-in user only has access to the records they need. This is accomplished via “sharing” in Salesforce. Every top-level and inner Apex class has a sharing specification (with sharing
, without sharing
, or inherited sharing
), even if only implicit. with sharing grants the current user access only to those records they own or have been shared with them. It should be used whenever possible.
The wrong specification potentially grants a user visibility to records they should not see. Every developer should understand the implications of Apex class sharing specifications, including how to unit test them using the System.runAs
method.
CRUD (Create / Read / Update / Delete) Checks
Beyond simple visibility, you’ll need to be aware of the database operations available to the current user for a given record. Apex code always executes in system mode, which means any running user may perform any operation. Restrictions need to be added by the developer. Actual permissions for the running user must be checked prior to each access using features such as DescribeSObjectResult, SOQL “WITH SECURITY_ENFORCED”, or recently-added features for enforcing user mode.
FLS (Field Level Security) Checks
Similarly, a user’s access to various fields must be explicitly checked in Apex. Again, Salesforce provides features such as DescribeFieldResult and user mode enforcement for checking FLS permissions within Apex.
As with record access, CRUD and FLS restrictions can be unit tested using the System.runAs
method.
Apex + UI Issues
There are several potential security risks for Apex controllers of UI elements (Visualforce pages, Aura components, and LWCs). Salesforce goes along way toward preventing Cross Site Scripting (XSS), Cross Site Request Forgery (CSRF), and SOQL Injection attacks, but there are still vulnerabilities that Apex developers must be aware of. Again, Salesforce provides good documentation and Trailhead resources for getting up to speed.
Besides the links provided above, Salesforce has some excellent content to help with understanding and resolving security issues in Apex. It’s highly recommended that every developer earn the Security Specialist Superbadge on Trailhead. The Apex BOOST Library contains utilities that make secure coding simple and consistent for your team.
The next category of Apex errors include various exceptions and runtime errors that can occur when code gets executed.
Code / Runtime Errors
Let’s take a look at a concrete example of an error that might occur in Apex code.
public void myMethod(Contact person) {
if (person.Id == currentContactId) {
...
If left as-is, Salesforce will throw a NullPointerException when line 2 is executed if “person” is null. If this method is called in the course of handling a trigger event (i.e. user edits a contact record), a cryptic message will be displayed to the user and the record will not be saved. If the current transaction is a batch process or some other asynchronous context, an email may be sent to a few admin users, again with a very unhelpful message.
This is almost certainly a place where we need to look at some alternatives for handling a potential error.
Throw an Exception
A common approach to error handling is to throw exceptions. This works well for LWS and Aura component controllers, as well as trigger handlers. Throwing an exception is also a good approach for code that will be a level or two down in the stack trace – for example, if the user clicks a button that invokes an Apex controller, which then calls myMethod. This effectively “passes the buck” of dealing with the error to code higher in the stack trace.
One approach would be to allow Apex to throw it’s own, NullPointerException (lines 1-3 below). A more deliberate and consistent approach in this case, would be to throw an InvalidParameterValueException
. In either case, document your method with a @throws
annotation to make it clear.
// @throws NullPointerException if person is null
public void myMethod(Contact person) {
if (person.Id == currentContactId) {
...
// @throws InvalidParameterValueException if person is null
public void myMethod(Contact person) {
Util.throwBadParamIf(null == person, 'person is required');
...
Below is a sample implementation of throwBadParamIf
.
public class Util {
// @throws InvalidParameterValueException with 'message' if condition is true */
public static void throwBadParamIf(Boolean condition, String message) {
if (true == condition) {
Exception e = new InvalidParameterValueException(null, null);
e.setMessage(message);
throw e;
}
}
}
Hide / Ignore
Hiding errors in software is sometimes referred to as “defensive programming.” Hiding is commonly accomplished using conditional statements or a try/catch block. Code is added that detects the problem, then silently ignores it. While this may occasionally be a valid strategy, it’s most often a red flag that a potential error is being masked and needs some attention. Again, if this is by design, at least provide documentation.
// @param person; if null, this method does nothing
public void myMethod(Contact person) {
if (person == null) return;
// @param person; if null, this method does nothing
public void myMethod(Contact person) {
try {
if (person.Id == currentContactId) {
...
} catch (NullPointerException e) {
}
Return an Error Code
A less common error handling technique for non-controller code is to return a special error value. This has some disadvantages over other methods: error codes can more easily be ignored, and if other information needs to be returned, a new structure may be needed. Error codes should be returned from Apex REST and SOAP endpoints.
// @return NULL_PARAM if person is null
public ErrorCode myMethod(Contact person) {
if (person == null) return NULL_PARAM;
Conform to Platform Patterns
Salesforce has suggested error handling patterns for several UI paths. These should be used when Apex code directly (method is invoked by flow, LWC, trigger, etc.) supports one of these.
- Flow (
@InvocableMethod
): Throw any type of Exception. The exception will be translated to a{!$Flow.FaultMessage}
which can be used to populate a screen / email / log within the flow. - LWC controller (
@InvocableMethod
): Throw any type of Exception, then add Javascript logic to display it to the user or provide other handling. - Aura component handler: Throw an AuraHandledException from an @AuraEnabled method to handle errors in an Aura component.
- Trigger handler: Use
SObject.addError
andSObjectField.addError
to display errors directly to the user. - Visualforce controller. An
ApexPages.addMessage
call, combined with an<apex:pageMessages/>
element on a Visualforce page should be used to display error information to the user.
Summary
Errors cause all kinds of grief for customers, consultancies, and developers. Life is much better when they’re handled well. Hopefully we’ve covered a few ideas here that will motivate you to look more carefully at the errors that may be lurking in your Apex code.
In case you haven’t heard, there are several tools that can be helpful reminders of some of the errors in your code. One is PMD, a static analysis tool you can run as part of your development, source control, or CI process. Another is the Force.com Security Scanner.
Finally, enhanced and simplified error handling is one of the primary purposes for the Apex BOOST Library. Give it a try today and let us know what you think!