When working with asynchronous processes in Salesforce — such as Queueable Apex — it’s often important to control what happens after the job completes, whether it succeeds, fails, or gets aborted.
That’s where the Transaction Finalizer comes in. It allows you to easily define what should happen once your job completes, no matter the outcome.
What Is a Transaction Finalizer?
A Transaction Finalizer is a Salesforce Apex feature that enables developers to define post-execution logic for asynchronous jobs. It is implemented using the System.Finalizer interface, which is currently supported only in Queueable Apex.
This interface requires you to implement the execute() method, which runs automatically after the asynchronous job completes.
Here’s the basic structure of a Finalizer:
public class MyFinalizer implements Finalizer {
public void execute(FinalizerContext fct) {
//Logic that runs after the async job finishes
}
}
The execute(FinalizerContext fct) method runs automatically for every Queueable job that has a finalizer attached. Within this method, you can define actions to take after the Queueable job finishes — such as:
- Logging results
- Handling errors
- Performing cleanup
- Retrying the job if necessary
What Happens Behind the Scenes
Here’s the lifecycle of a Queueable job with a Finalizer attached:
- You enqueue a Queueable job using System.enqueueJob().
- The job executes asynchronously in its own transaction.
- If a finalizer is attached (via System.attachFinalizer()), Salesforce invokes its execute(FinalizerContext) method after the job finishes — regardless of whether it was successful, failed, or aborted.
- The finalizer then runs in a new, independent transaction with its own set of governor limits.
Example Scenario
Let’s look at a simple example to see how this works in practice.
In this scenario, we’ll create a Queueable class that performs a division operation. We’ll intentionally start with a case that causes an exception (b = 0), and then use a Finalizer to handle the failure and retry with a valid value.
public class MyQueueable implements Queueable, Finalizer {
private Integer a;
private Integer b;
public MyQueueable (Integer a, Integer b) {
this.a = a;
this.b = b;
}
public void execute (QueueableContext qct) {
System.attachFinalizer(this);
try {
Integer x = a / b;
System.debug('x = ' + x);
} catch(Exception ex) {
System.debug('Exception message: ' + ex.getMessage());
throw ex;
}
}
public void execute (FinalizerContext fct) {
if(fct.getResult() == ParentJobResult.SUCCESS) {
System.debug('Parent queueable job [' + fct.getAsyncApexJobId() + '] completed successfully.');
}else{
System.debug('Parent queueable job [' + fct.getAsyncApexJobId() + '] failed due to unhandled exception: ' + fct.getException().getMessage());
System.enqueueJob(new MyQueueable(10, 2));
}
}
}
How This Code Works
We start by enqueueing the job with a = 10 and b = 0:
System.enqueueJob(new MyQueueable(10, 0));
Since dividing by zero causes an exception, the Queueable job fails. When the job fails, the attached finalizer automatically runs in a new transaction. Inside the finalizer, we check the job result. Because it failed, we log the exception and re-enqueue the job with corrected values (a = 10, b = 2). The new job then executes successfully, and the finalizer logs a success message.
Key Takeaways
- The Transaction Finalizer always runs after your async job, regardless of outcome.
- It runs in a new transaction, giving you a clean context and fresh governor limits.
- You can use it for logging, cleanup, error handling or automatic retries.
- Combining Queueable and Finalizer makes your asynchronous processes more resilient and self-healing.
Top comments (0)