When working with asynchronous processes in Salesforce, such as Queueable Apex, it’s often crucial to control what happens after the job completes — whether it succeeds, fails or gets aborted.
That’s where the Transaction Finalizer comes in. It lets you easily define what should happen once your job completes, no matter how it ends.
What Is a Transaction Finalizer?
A Transaction Finalizer is a Salesforce Apex feature that allows you to define post-execution logic for asynchronous jobs. It’s implemented by using the System.Finalizer interface — currently supported only in Queueable Apex.
The interface requires you to implement the execute() method, which runs automatically after the async job completes.
Here’s the basic structure of a Finallizer:
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 the execute() method, you can define the actions that should take place once the Queueable job finishes — such as logging results, handling errors, performing cleanup, or even re-enqueuing the job for retry.
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 — whether successful, failed, or aborted.
- The finalizer runs in a new, independent transaction, with its own set of governor limits.
Example Scenario
Let’s look at a simple example to see this in action.
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 will fail.
When the job fails, the finalizer attached to this job will automatically run in a new transaction.
Inside the finalizer, we check the job result — Since it failed, we log the exception and re-enqueue the job again with new values (a = 10, b = 2). This time, the division succeeds, 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)