What’s trending
UPCOMING EVENTS
Master Queueable Apex: When, Why, and How to Use It
By Jumber Mdivnishvili
Almost every Salesforce project requires logic that performs asynchronously in the background that handles complex operations and/or do chaining; Queueable Apex is a great tool for this.
In this article, we will cover Queueable Apex from the basics to advanced concepts. Whether you’re a seasoned developer or just starting, this is your opportunity to master this essential tool!
Why and When Should I Use Queueable Apex?
This article is about Queueable, but to understand when to use it, it is important to distinguish between Queueable and other asynchronous “flavors”.
Future Method vs. Queueable
In my opinion, Queueable Apex is an improvement of the Future method – I would only use the Future method over Queueable in test classes to overcome mixed DML Error.
We cannot monitor the process executed in the Future method as we don’t have a direct way of seeing whether it is still running or is completed. However, we can monitor the Queueable job and control the flow of our application based on that.
Future methods can only accept primitive types or collections of primitive types. They cannot efficiently handle complex types, unlike Queueable, which supports them seamlessly.
Also, Future method cannot call another Future method. But, from Queueable we can start another Queueable which allows us to achieve chaining, which we will discuss later in the article.
Batchable vs. Queueable
Batch and Queueable serve different purposes. Batchable is intended to be used when we want to operate on large amounts of records (up to 50 million). Queueable is not intended to be used in that way, as Queueable is much faster than Batch.
We can start only five Batch jobs from synchronous transactions compared to 50 Queueable jobs – this is why it is not advised to use Batch in triggers.
So the final answer to when to use Queueable is:
- When we need to run the asynchronous process in the background efficiently – even from triggers.
- When we don’t need to process millions of records.
- When we need to chain Queueable jobs.
Get Started With Queueable
If you know the basics of OOP, you must be familiar with the concept of interface. Queueable is also an interface that we must implement in our class to make it Queueable.
The interface makes us obliged to use the method void execute (QueueableContext qc
). We can see there is one parameter of type QueueableContext and it contains the ID of the current Queueable job. We can access that job ID with the method: qc.getJobId()
.
A Queueable job starts only after the transaction that initiated it has ended. If the transaction is ended with an exception, the Queueable job will not start. So based on what we have discussed so far, a basic Queueable class would look like this:

We can just normally call the execute method like this:

However, that is not how the Queueable job actually starts – this will just invoke a method like the usual method but the asynchronous job will not be fired. The proper way to start a Queueable job is by using System.enqueueJob()
method like this:

This will return the Job ID of this Queueable job which we just have initiated. We can store it in a variable if we need it:

jobId
can be used to query AsyncApexJob representing our Queueable job:
[SELECT Id, Status FROM AsyncApexJob WHERE Id = :jobId]
The status field can tell us about the status of the job and if it is completed or failed.
What Exactly Is Queueable “Job”?
The class implementing the Queueable interface is available for creating Queueable jobs. Each enqueueing of the job is creating a new job:

Although I am utilizing the same Queueable classes in the above example, they are considered as five different jobs. Jobs are listed in Setup → Environments → Jobs → Apex Jobs:

We can view and abort jobs from here, or we can abort programmatically:

Queueable Job Use Case
Let’s say you, as a developer, are assigned to the user story which has the following requirements. Every time Contact records are created, some logic should be executed. Callouts to an external system must be done for each record and response must be handled. If 500 Contacts are created, callout must be done 500 times.
Just stop for a minute and think, how would you approach this problem? Queueable is the way to go as it allows us to chain jobs.
For example, triggers are executed in chunks of 200 records. If 500 records are inserted, the triggers will run in three batches: two with 200 records each and one with 100 records. Despite running three times, it is still considered a single transaction and operates within a single execution context.
Let’s say 500 contacts are created. From the trigger, we can call Queueable. It will do callouts for 100 records, and the next 100 records will be passed to the chained job.
Then the trigger will receive another 200 record chunk and the same will be done for it. Finally, the next 100 record chunk will come – for that, no chaining will be needed as one Queueable job can handle 100 callouts.
Before moving on to the example I want to point out two things:
- If you want to be able to do callouts from the Queueable, then Database.AllowsCallouts must be implemented too, as will be shown in the example below.
- In the example, I also used Finalizer, which I will explain later on.

Note: The purpose of this example is to show how Queueable works, so code is simplified (for example Trigger directly calls Queueable class, it does not have any trigger helper or handler) to not overwhelm you with other details.

As you can see, we have a loop that will run only 100 times if the amount of records passed to Queueable through the constructor is greater or equal to 100.
If there are fewer than 100 records, then the loop iteration will be equal to the record count. So technically, the loop will run a maximum of 100 times, thus a maximum of 100 callouts will be done. After each callout, we are removing a record from the list.
Then we check if records are still remaining in the list. If yes, we are chaining and then initiating the next Queueable job from the Queueable job and passing the remaining records to it. Queueable jobs will be chained until we have no remaining records.
Chaining has no limit, so we can chain as many jobs as we want. However, it is not the same for dev and trial orgs. In dev and trial orgs, the chain limit is five. So we can have five jobs chained, including the first job, which is the start of the chain.
In a synchronous context, we can initiate a maximum of 50 Queueable jobs. For example, from a trigger, we can start 50 Queueable jobs maximum. From the Queueable job, we can initiate only one Queueable job.
Finalizer
You might also notice we have something called Finalizer, which is an interface and it should be implemented by the Queueable class itself or by the inner class.
With the help of Finalizer, we can execute some logic whenever our Queueable is ended. This means we can now listen to the completion of the Queueable job and do things like send emails after Queueable is finished or log an error if the job ended with an exception, and so on.
We can even call another Queueable from Finalizer. We can access details of the Queueable job to which Finalizer was attached by using calling methods on a parameter of execute()
method of Finalizer.
A very crucial ability of Finalizer is also the ability to handle Limit exceptions, which potentially can occur in Queueable jobs. We would not be able to handle them without Finalizer.
AsyncOptions and AsyncInfo
AsyncOptions and AsyncInfo built-in classes help us to customize the behavior of our Queueable jobs.
AsyncOptions class properties:
Property | Purpose |
---|---|
DuplicateSignature | Allows to store a unique signature, which will be built using built-in QueueableDuplicateSignature and Builder classes, and attach it to the job. We are signing the Queueable job and it ensures that only one job with this exact signature will run. Signature can be created based on different things, depending on your use case and creativity. For example, It can be created based on UserId + Apex class Id + Data. |
MaximumQueueableStackDepth | Salesforce has a daily limit of asynchronous operations (Batch, Future, Queueable, Schedulable), which is 250,000, or the number of user licenses multiplied by 200 – whichever of these two numbers is greater. This limit resets every 24 hours. In order to avoid hitting this limit, some projects might require specifying the maximum stack depth of Queueable jobs, and the depth of chaining. We can specify the maximum depth with this property. |
MinimumQueueableDelayInMinutes | Sets minimum delay of Queueable job. So total delay time would be when resources will be available + time specified in this property. |
We can read information about the state and characteristics of our Queueable job using
AsyncInfo class:
Method | Purpose |
---|---|
getCurrentQueueableStackDepth() | Returns the current stack depth we are at. |
getMaximumQueueableStackDepth() | Returns maximum stack depth for Queueable job. |
getMinimumQueueableDelayInMinutes() | Returns minimum delay for the Queueable job. |
hasMaxStackDepth() | Returns Boolean value indicating whether the Queueable job has specified max stack depth or not. |
Defining Max Stack Depth

So we created an object of AsyncOptions and specified the maximum stack depth. Then we passed the AsyncOptions object to System.enqueueJob()
as a second parameter to specify that this concrete job should run according to the specified options.
This is how we check stack depth before doing chaining (executing another Queueable from the Queueable):

Creating Signature
The below code shows how to create a unique signature for our Queueable job. As you can see, we are leveraging the DuplicateSignature property to store signatures on AsyncObject.

Let’s try to find out what is the “anatomy” of the process of creating the signature. DuplicateSignature property is the type of QueueableDuplicateSignature.
Builder is the inner class of QueueableDuplicateSignature, so addInteger()
, addId()
, addString()
, and build()
methods are the methods of the Build class. The method build()
in the end returns the QueueableDuplicateSignature object.
Setting Minimum Delay
Besides specifying minimum delay using AsyncOptions, there is also another, older way of doing that. Below are examples of both ways:

Summary
Queueable is a very useful and powerful feature of Apex, which lets us run asynchronous logic, chain jobs, and monitor them, which lets us create robust code.
Queueable is an improvement over the Future method. It eliminates limitations such as the inability to receive complex types and the lack of chaining capability, etc.