Future vs Queueable
Introduction
Oftentimes, we need to do an external callout when a trigger transaction is completed, or make an update that requires creating a new transaction, do some summaries at the end of the process.
To do all this stuff, usually, developers would use the @Future method in the first place. But is it the best solution? In this post, I will briefly present future methods and Queueable implementations, then compare them.
Have a good read!
About @Future annotation
The easiest way of achieving asynchronous apex execution is using @Future annotation. By simply adding it to a method, we can run code in an async context.
Implementation
It’s pretty straightforward:
@Future
public static void asyncMethod(Id recordId, List<String> primitiveDatatype) {
// do something
}
In case of making a callout we would need to use @Future(callout=true)
.
Then we can execute it:
MyClass.asyncMethod(account.Id, accountAddresses);
Summary
The rules are simple:
- Method has to have the @Future annotation,
- If a callout is being made, the @Future(callout=true) annotation is needed (by default, it is callout=false)
- Only data structures (like lists and maps) of primitive types and primitive data types can be passed as parameters
- Cannot call other future methods, each method is executed in its own asynchronous context
Future methods execute in an asynchronous context, so that the time of execution is not guaranteed. Asynchronous methods run when system resources are available.
Queueable Apex in-depth
Queueable is yet another tool that Salesforce offers for asynchronous processing. It is very similar to @Future methods, but it offers a lot more functionality. Some aspects of the Queueable interface are unique to it own. Simply put, Queueable is more advanced than @Future in most use case scenarios!
The Basics
As you will see, we can pass complex data to Queueable constructor, but we need to implement an entire class.
Implementation
public class AsynchronousExample implements Queueable {
private List<SObject> data;
private Id parentId;
public AsynchronousExample(List<SObject> records, Id id) {
this.data = records;
this.parentId = id;
}
public void execute(QueueableContext context) {
// logic
}
}
Starting Queueable job
The simplest way is to use System.enqueueJob
:
System.enqueueJob(new AsynchronousExample(records, parentId));
We can also delay the start of Queueable job, for example when integration requires it:
Integer delay = 5; // 0-10 minutes after queueable will be run
System.enqueueJob(new AsynchronousExample(records, parentId), delay);
Or make use of the AsyncOptions
class to make additional validations for Stack Depth:
public class AsynchronousExample implements Queueable {
private static final Integer MAX_DEPTH = 4;
private List<SObject> data;
private Id parentId;
public AsynchronousExample(List<SObject> records, Id id) {
this.data = records;
this.parentId = id;
}
public static void runJob() {
AsyncOptions asyncOptions = new AsyncOptions();
asyncOptions.MaximumQueueableStackDepth = MAX_DEPTH;
System.enqueueJob(new AsynchronousExample(records, parentId), asyncOptions);
}
public void execute(QueueableContext context) {
List<Sobject> results = doSomeLogic(data, parentId);
if (System.AsyncInfo.hasMaxStackDepth() && AsyncInfo.getCurrentQueueableStackDepth() >= AsyncInfo.getMaximumQueueableStackDepth()) {
insert results;
} else {
System.enqueueJob(new AsynchronousExample(data, parentId));
}
}
private List<SObject> doSomeLogic(List<SObject> records, Id id) {
// some secret logic
}
}
Okay, we know how to implement it, but does it have any more tricks in its sleeve? The first and obvious one is that we can use complex data types as parameters. Anything else?
Return Job Id
When we enqueue a new Queueable job, we get its Id in return. Why is this important? Remember, Salesforce gives you a promise that an asynchronous job will be eventually run when the resource will be free. But when is that? That’s the catch. Using JobId
we can verify what happens with our enqueued job.
How to do it?
Id jobId = System.enqueue(AsynchronousExample());
AsyncApexJob jobInfo = [SELECT MethodName, Status, NumberOfErrors FROM AsyncApexJob WHERE Id = :jobId];
Learn more about AsyncApexJob
here.
Chaining Queueable job
The first advantage of the Queueable interface is that it can chain jobs. Do you remember that Future can’t call other Future methods from its own? When running Queueable we can add one more Queueable to the execution queue. Is it a big deal? Yes, it is. It allows us to optimize our execution and split data into simpler blocks, which helps with preventing hitting Governor Limits.
Example use cases:
-
Imagine situation when you have to update contacts based on the account data. It makes sense to split data where all contacts of a given account are in the same chunk.
-
Or we are doing multiple callouts that ones data is required for the other ones, when we split them into separate Queueable transactions, we are getting new Limits for each of the callouts.
How to queue next job?
public void execute(QueueableContext context) {
// do some logic
// in execute method, queue next job (but only 1!)
System.enqueue(new AsynchronousExample(records, id));
}
Detecting Duplicated Queueable Job
Queueable comes with a great tool AsyncOptions
class. Recently, it received another great feature. Now you can add a signature to your Queueable execution, to make sure you are neither wasting resources nor running jobs for the same records twice.
How does it work? Imagine you have Queueable, that uses an account as start point:
public static void runJob(Id accountId) {
Account account = [SELECT Id FROM Account WHERE Id = :accountId];
AsyncOptions options = new AsyncOptions();
options.DuplicateSignature = QueueableDuplicateSignature.Builder()
.addInteger(System.hashCode(account))
.addId(accountId)
.build();
try {
System.enqueueJob(new AsynchronousExample(accountId), options);
} catch (DuplicateMessageException ex) {
// Now you know that job for this accountId is already enqueued
}
}
From the code above, when QueueableDuplicateSignature
is added, we can catch DuplicateMessageException
. When the exception is thrown, we are sure that the job for this record is already enqueued.
Transaction Finalizer
What’s that? It’s neither Future method nor Queueable! Yes, it’s not. But System.Finalizer
interface is a great addition for your Queueable classes. It gives a Queueable superpowers!
Implementation:
It is similar to Queueable implementation, we need to implement execute
method:
public class FinalizerExample implements Finalizer {
public void execute(FinalizerContext context) {
System.debug('It\'s not even my final form!');
}
}
How will Finalizer know when to be executed? We need to attach it to Queueable job (only one can be attached):
public class AsynchronousExample implements Queueable {
public void execute(QueueableContext context) {
System.attachFinalizer(new FinalizerExample());
// rest of logic
}
}
Now each time our Queueable finishes its execution, Finalizer will be run (even when it fails).
Methods
As you probably already found out, in Finalizer implementation we are using new context. It comes with few additional methods:
Method | Functionality |
---|---|
global Id getAsyncApexJobId |
ID of the Queueable job which Finalizer is attached to, used to identify job in AsyncApexJob table. |
global String getRequestId |
Unique Id that identifies current request, Queueable job and Finalizer both have the same request Id. |
global System.ParentJobResult getResult |
Returns ParentJobResult enum, possible values are SUCCESS , UNHANDLED_EXCEPTION |
global System.Exception getException |
If an exception occurred, it will return the type of exception. |
Handle Limits exception
Have your head about the terrifying System.Limits
exception? You don’t have to worry about it any more! System.LimitException
, can’t be usually handled, but what if we use Finalizer, let check out!
I’m creating a dummy Finalizer, just to check if we can run the job oin case of failure:
public with sharing class FinalizerExample implements Finalizer {
public void execute(FinalizerContext context) {
System.debug('Job status: ' + context.getResult());
}
}
And even worse Queueable implementation:
public class QueueableExample implements Queueable {
public void execute(QueueableContext context) {
System.attachFinalizer(new FinalizerExample());
for (Integer i = 0; i < 201; i++) {
new List<Account>([SELECT Id FROM Account]);
}
}
}
And without surprise, we got exception:
But what’s that? Did we get Success next?
Yes, we have! It’s a message from the Finalizer. Now we can control what happens next, do we send an email with a bug? Requeue job with fewer records? Or just finish there? Thanks to transaction Finalizer we have new options.
For another example, check documentation, you will find there logging example using Finalizer.
If you face any issues with attaching Finalizer to your Queueable job, refer to this Developer Guide page.
Testing Asynchronous Methods
Lastly, of course, we need to check if the code we wrote is working as expected. In both cases, testing is fairly simple:
@IsTest
private class MyTests {
@IsTest
private static void testFuture() {
// prepare some data like Account record
Test.startTest();
AsynchronousExample.asyncMethod(account.Id, new List<String>{ 'Some', 'Test', 'Strings' });
Test.stopTest();
// verify @Future method results
}
@IsTest
private static void testQueueable() {
// prepare some data like child records and parent id
Test.startTest();
System.enqueue(AsynchronousExample(contacts, account.Id));
Test.stopTest();
// verify Queueable class results
}
}
When testing asynchronous apex, you should always use Test.startTest
and Test.stopTest
methods. Invoking Test.stopTest
makes sure all long-running operations are finished, and you are able to retrieve results from the database.
Summary & Conclusion
As you can see, Future is a fairly simple and easy way to run some code in the asynchronous context. But on the other hand, Queueable is a much more sophisticated solution that offers much more of a functionality. Not only it extends the base functions of @Future annotation, but offers unique aspects. We are able to enqueue another job from inside, check current status or rerun a failed job. All of it is lacking in the @Future. In my mind, in the most cases, we should opt for using Queueable. Its functionalities enable developers to build more robust code which is easily extendable and adoptable.
What makes Queueable better
Most of the Queueable functionalities came from the System.AsyncOptions
class and transaction Finalizers. You know about Finalizers already, but remember that System.AsyncOptions
provides three powerful functionalities:
Property | Functionality |
---|---|
MinimumQueueableDelayInMinutes | Allows setting minimum delay time of Queueable job. It serves a purpose during callouts, when multiple jobs are enqueued at the same time, and we want to make sure that we are not overloading the external system. Salesforce Administrators can also specify org-wide delay, more on that in the documentation. |
MaximumQueueableStackDepth | Is more of an optimization tool for developers. Prevents too many nested Queueable execution in one transaction. Developers can set the desired stack before execution, preventing Limit exception. |
DuplicateSignature | Is also an optimization tool. The developer can implement a signature for a Queueable job, which is used during Queueable execution to prevent running code for the same data. It’s especially useful when we have Queueable with multiple entry points (places where we enqueue it), with a good signature we can ensure that the job will be run only once for selected records |
When to use what
As I wrote, we should use Queueable in the most cases, but when to use @Future then?
Use Case | Method |
---|---|
Callout for external data or simple operations | Future |
Trigger update on the same object and other workarounds (Warning! Potential exception can occur when recursion happens!) | Future |
Also for callouts, when multiple long-running timeouts are needed, but also useful for a robust integration frameworks thanks to easy processing control, deduplication capabilities and easier logging. | Queueable |
When big amounts of data needs to be processed. @Future method gives us only one asynchronous transaction. Meanwhile, in Queueable we can split data into chunks and process them in multiple chained jobs. | Queueable |
Critical operations, thanks to Finalizer we can reschedule a job, even if we got Limits exception. | Queueable |
When we require asynchronous methods with better observability and optimization. Simple returning JobId gives a lot of insight on what is going on with the job. |
Queueable |
Sources
- Apex Developer Guide – Future Methods
- Apex Developer Guide – Queueable Apex
- Apex Developer Guide – Detecting Duplicate Queueable Jobs
- Apex Developer Guide- Transaction Finalizers
- Apex Reference Guide – Queueable Interface
- Apex Reference Guide – AsyncOptions Class
- Control Processes with Queueable Apex – Trailhead