Apex is a unique skill that developers must master when creating custom solutions with Salesforce. There are some basics that are essential. Certain things, like syntax and control flow, are like learning any other language. Other things, like bulk operations, governor limits, and triggers, are particular to Apex and how it is implemented on the Salesforce Platform. Once you have a grasp of these key features and idioms, where do you go next?
In this post, you’ll learn about a few Apex features that are critical for any Apex developer who is looking to uplevel beyond the basics, along with where you can go to learn about them.
Note: Where possible, the code examples in this post are taken directly or adapted from the Apex Recipes sample app or the Apex Developer Guide. See the references at the end of the article.
Extending Flow with invocable Apex
These days, if you want an automated process or a wizard-like UI, Flow should always be an option that you consider. The scope, flexibility, and maturity of Flow has grown immensely. Still, it is rare that complex business problems can be solved without code at all.
Enter invocable Apex.
@InvocableMethod is used to define methods called by Flow. And
@InvocableVariable surfaces parameters for Flow to pass data to your Apex code. Why would you call Apex from Flow? Perhaps the logic of your flow is becoming too complex. Maybe your automation needs to perform an operation that’s not accessible to Flow. In cases like these, Apex can be your friend.
Here’s a basic illustration of using the
This example from the Apex Developer Guide illustrates how the annotation marks this as a method that’s invocable by Flow. Notice that the annotation also defines details like a label and description that determine what gets surfaced to the Flow Builder user.
An important side-effect of the
@Invocable… annotations that few people talk about is that this automatically surfaces your Apex to the
<INSTANCEURL>/data/<VERSION>/actions/custom/apex API endpoint.
One other useful thing that you can do by invoking Apex from your flow is to access the different types of asynchronous Apex. Which brings up the next Apex feature to address.
Tapping into asynchronous Apex
Asynchronous execution decouples an action that we want to take from the current execution context. Following current best practices, there are three ways of executing Apex asynchronously: queueable, schedulable, and batch. While technically the event bus is asynchronous, we’ll skip that for the moment.
If you just need to fire a bit of code to execute asynchronously, queueable is the go-to tool. If you have multiple asynchronous tasks, queueable deterministically executes them in order. Because they are queued in order, you can also chain one async call from another. And queueable Apex supports inputs of either object or primitive types.
Scheduled Apex works just like you would expect: run some Apex code on a schedule. Batch Apex is optimized for working with very large data sets. To use asynchronous Apex, you need to create an Apex class that implements a specific interface —
Note: Some developers may be familiar with the
@Future annotation to make a single method asynchronous, aka “future methods.” Future methods, while quick and easy, have limitations that make them less useful. There is nothing you can do with a future method that can’t be done better with a queueable class. For this reason, we do not recommend production implementations using future methods.
So, let’s take a look at an example of a queueable Apex class.
All asynchronous Apex interfaces have an abstract
execute() method that needs to be implemented as shown above. To run your
Queueable class, you need to enqueue it.
While not shown in this example from the Apex Recipes sample app, to pass data into your queueable, override the constructor with the parameters you need.
Data abstraction with “dynamic” Apex
The term “dynamic Apex” is a bit of a misnomer. Or, at the very least, it could be misconstrued as Apex somehow performing like a dynamic programming language. This isn’t what this refers to (for instance, there is no Apex
Dynamic Apex is code that adapts to different SObject types (
My_Custom_Object__c, etc.) at runtime. This is done by abstracting away specific SObject references. The key is to use the
SObject superclass when working with data. You will also use the Apex “describe” features to detect which objects and fields you are working with and what you can do with them (is this user allowed to query this object, for instance).
This is an essential skill for ISV developers. Often, you’ll create an app that needs to adapt to the local environment of the customer that installs your app. This means that your code needs to detect, describe, and adapt its behavior depending on known, or even unknown data.
Although it is essential for an ISV developer, it is also important to anyone who wants to write more reusable code.
Take this very basic query example:
This will work all day long querying for Account data. But what if we want to construct a query for any SObject?
To make this into “dynamic” Apex, we need to abstract away the specific SObject and field references. And perhaps we’d wrap that in a method that allowed another developer to pass those in. We might also check to ensure that the user has the object permissions to do the operation we’re about to try.
The above constructs a query for an SObject that is passed into the method, and an arbitrary list of fields. It uses the
DescribeSObjectResult object to detect queryability and infer the SObject API name when constructing and running the query. You then need to invoke it.
Here’s how we might do that with the Account and Contact objects, respectively:
This example doesn’t address some risks of dynamic SOQL, such as SOQL injection. So, be certain to read up on dynamic Apex. Learning to write secure Apex will make you less likely to deploy vulnerable code to your (or a customer’s) org. Variable binding in your SOQL queries is a key tool to protect your dynamic Apex from being exploited. The
escapeSingleQuotes() method is another, older tool that you may also encounter. User mode is also key to ensuring any action taken has to adhere to any and all restrictions of the executing user.
For a more well-rounded and secure example of dynamic Apex, look at the invocable method Apex recipe in the Apex Recipes sample app. That invocable action works with either the Account or Task object.
Integration and Apex
With Salesforce, there are a few ways you can do integration without code. Specifically, if you just need a client app or service to get data from Salesforce, there are a lot of APIs that are automatically surfaced without any coding on the Salesforce side. They even adjust themselves to your custom schema. If you are integrating your org to an OData-compliant external system, you can use Salesforce Connect and external objects (again, no code required).
But sometimes, you may need to call out from your org to an external service. Or maybe you want to surface a custom endpoint that bundles more complexity than one of the standard APIs. Here again, Apex contains features that support both the surfacing of new custom APIs and calling out to web services.
When thinking of custom APIs, these days, most developers would think of REST APIs. You can build a custom REST API on the Salesforce Platform by creating an Apex class that is annotated with the
@RestResource annotation. Part of the annotation’s job is to also define the resource name. All Apex-based custom REST APIs are found at
To define the behaviors that you need to support, there are a series of method annotations that correspond to HTTP verbs, such as
RestContext class contains a set of accessors to get the state of the incoming HTTP request and the outgoing response that you return. Below is an example of a basic REST endpoint surfaced to the HTTP GET method. Note how the annotations are used.
If your requirement is to create a SOAP service, there are a set of features to support that protocol as well.
Outside of the actual code, you’ll need an authorized session to access any web service you create. But if you already have an authorized client, the request is just another call to the custom endpoint you define.
Calling an external service
Calling out to an external REST endpoint is done via a set of classes. The
HttpRequest class allows you to define the parameters for how your request will behave (endpoint, HTTP verb, headers, etc.). You make the request via the
Http.request() static method. This returns an
HttpResponse object which provides you with the returned data or error if the request fails.
HttpResponse classes are the same for both REST callouts and custom REST services.
There’s some configuration required for your callout. Specifically, you’ll need to set up a named credential in most instances. This configuration allows you to offload the authentication handshake from your code. Although, if you’re calling an anonymous unauthenticated service, you can also opt for using a more simple remote site configuration.
The above example makes a simple callout to an endpoint. Note there’s no use of the options to set headers, or use a named credential as this request is to a simple API that requires no authentication.
Hopping on the event bus
Event streaming is a valuable tool in creating loosely coupled architectures in complex applications. The Salesforce Platform implements event streaming through two main features: platform events and change data capture. The backbone of these features is the event bus.
From the Platform Event Developer Guide, the event bus is “A multitenant, multi-cloud event storage and delivery service based on a publish-subscribe model.” Both your org and external systems are able to publish and subscribe to events. Of course, it’s key that if you publish a message, something needs to have subscribed to it for anything else to happen.
Platform events represent the data that is passed through the event bus. Events are processed in the order they’re received. One way to think of them is as an ephemeral SObject that is temporarily stored on the event bus on its way to its destination. In fact, the metadata for an event is stored in the same place in your project as your SObjects. You can distinguish custom platform events from SObjects by their
__e suffix (for instance
Once you populate an event with its data, the
EventBus.publish() method is analogous to a DML call on your normal SObject. Note the code below which publishes an event.
In Apex, you subscribe to an event by creating an async trigger for it. But async triggers are different from the standard transactional DML operation of a normal trigger. For this reason, you need to be aware of some differences. With async triggers, batch size is different. You need to configure your running user. Event trigger operations can also be retried. Make sure that you learn about these differences as you start to implement platform event triggers. Note the trigger below.
Unlike typical DML triggers, platform event triggers only support the
after insert context. There is also no explicit action to subscribe a trigger to a platform event. Once the trigger is deployed to your org, it is automatically subscribed.
In addition to Apex, platform events can be fired and subscribed to by external systems and Flow. They are key in loosely coupled integrations. As such, it’s rare that Apex both fires and subscribes to the same event.
Further down the Apex trail
We’ve covered a lot. But by no means is this everything you need to know as an upskilling Apex developer. The items covered are based on some fairly common features. But you may need to learn other Apex features depending on your project requirements. Here are some other items you should look out for.
Platform Cache: If you’re coming from other programming platforms, you’ll be familiar with the notion of a global or static variable being persistent across transactions. But in Apex, statics are scoped to the transaction. Platform cache is an Apex feature that enables in-memory data persistence across transactions. If you’re finding performance lag related to data retrieval, platform cache may help.
Trigger frameworks: Not a language feature, but rather libraries that make using triggers easier. These community-created projects abstract away the boilerplate trigger code that you often need to write. By using a trigger framework, it should allow you to just focus on the logic that needs to be addressed. For anything more than a trivial trigger, adopting a framework is highly recommended. And I know some who would argue that you should consider a framework even for that trivial trigger. But this isn’t the place to debate time to value versus premature optimization. There is a very simple implementation of this in the Apex Recipes app.
SOSL: The Salesforce Object Search Language (SOSL) is SOQL’s underappreciated cousin. SOSL allows for non-deterministic text-based queries. It’s highly efficient at finding text data when compared to searching text fields with SOQL and wild cards. It supports searching multiple SObjects simultaneously and contains features to deterministically filter the search results. If you have text SOQL queries that are slow, definitely check out this tool.
AuraEnabled: For surfacing custom Apex code into your Lightning Web Components (LWC), this annotation is key. There’s a good chance that you’re already using this one if you started your Salesforce journey building LWCs. But if you’ve not used it yet, look for it in any project that involves custom UI. Why “Aura” enabled if it’s for LWC? Well, this annotation was initially created to support LWC’s predecessor, the Aura framework. It made sense not to reinvent a new annotation when the existing one would work just as well for LWC.
Summing it up
Every journey to learn a new programming language has some key features that everyone needs to know. Basic syntax, flow control, and the execution model of the platform are critical to grasp. But once you have those basics, working your way into more advanced features is key to growing your skill set. Invocable actions, asynchronous Apex, “dynamic” Apex, integration, and the event bus are all features that you’re highly likely to use as you progress. By familiarizing yourself with these now, you’ll not only prepare yourself to tackle projects in the future, but you’ll also be able to make better solution decisions.
About the author
Peter Chittum has worked in enterprise software and developer enablement for over 20 years. He’s spent the last 12 years helping thousands of developers be successful on the Salesforce Platform, most recently leading the Developer Advocate team at Salesforce. He spends his free time with his family, riding a mountain bike, cooking, and playing music. Follow him on Twitter (@pchittum), LinkedIn (in/PeterChittum), or BlueSky Social (@pchittum.bsky.social).