Skip to main content
Group

Apex Transaction Finalizers

Transaction Finalizers are an Apex feature in pilot starting in Spring '20. Use this group to share feedback with Salesforce, tips and tricks, or interesting use cases. It's here to keep an open channel for discussion about the ongoing development of the feature.

Resources from the Salesforce Developers Ask Me Anything on Apex Updates + Innovations

 

I hope you joined us for the fantastic SFDevsAMA on Wednesday 29th November featuring @Mohith Shrivastava and @Daniel Ballinger. They loved answering your questions about Apex. 

 

If you missed it, don't worry - we recorded the series and our experts have provided some great resources that our experts for you to continue your learning! 

 

Still have questions? Post them in this thread for our experts!Resources from the Salesforce Developers Ask Me Anything on Apex Updates + Innovations I hope you joined us for the fantastic SFDevsAMA on Wednesday 29th November featuring and .

@DataWeave in Apex @Salesforce Apex Hours @Apex Transaction Finalizers @Generics in Apex @User-Mode Database Operations

 

#Ask An Expert #CommUpdates #Apex #Apex Class #ApexDevelopment #Apexhours

12 comments
  1. Jul 5, 2025, 3:03 PM

    Using libraries like ApexMocks is now super verbose because of the lack of generics. 

    With generics, this:

    private static final MyService myService = (MyService) apexMocks.mock(MyService.class);

    could be written as this:

    private static final MyService myService = apexMocks.mock(MyService.class);

     

    And mock verification would change from: 

     

    ((MyService) apexMocks.verify(myService)).doSomething();

    to

    apexMocks.verify(myService).doSomething();
0/9000

@Srikanth Ganapavarapu I'm curious about the testing expectations of Finalizers. From what I've seen in examples and in my own implementation of the interface we should expect the Queueable.execute() and the Finalizer.execute() methods to complete synchronously between the Test.start() and Test.stop() methods of a test method. What I'm curious about is if we should expect all chained Queueables and Finalizers to complete synchronously when run between the start() and stop() Test methods? Should we write tests like the one in this example? https://developer.salesforce.com/blogs/2020/01/learn-moar-in-spring-20-implementing-promises-with-transaction-finalizers.html

Or should we use the Test.isRunningTest() method to stop the Finalizer from enqueuing the next Queueable and just assume that it will get enqueued like we would if we wrote a Queueable that enqueued a Queueable, like the docs state here? https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_queueing_jobs.htm

6 comments
  1. Sep 12, 2022, 8:50 PM

    @Drew Snyder 

    I haven't had a chance to validate this behavior. It has been a while that I looked into Transaction Finalizers. As a first step, I'll schedule sometime in last week of Sep (this month) to understand what is the current baseline behavior we have. I'll then review it with our PM @Daniel Ballinger. I'll try to get back to you by Oct 15th. 

0/9000

Have been doing some work with unit tests and finalizers. My understanding is that in general, finalizers get their own set of limits. However, in my testing, this is not the case in unit tests. In other words, if I create a limit exception (such as DML inserts) that ends with an exception, when the finalizer runs inside of a unit tests, it is unable to perform a DML operation as the DML limit has already been reached.

Is this expected behavior?

7 comments
  1. Oct 8, 2021, 7:26 PM

    Hi @Srikanth Ganapavarapu,

    thanks very much for responding. Here's my example.

     

    BTW, I'm really following up on @Daniel Appleman's first comment rather than the original post.

     

    Imagine a common logging framework storing in a custom object or something. This is intended to catch even Limit exceptions and make sure things are logged in a consistent way. Most exceptions can of course be caught, logged and re-thrown, but not the likes of CPU Time or Heap Size.

    public with sharing class FinalizerLogger implements Finalizer {

    public void execute(FinalizerContext context) {

    if (context.getResult() == ParentJobResult.UNHANDLED_EXCEPTION) {

    Id asyncJobId = context.getAsyncApexJobId();

    Exception exceptionThrown = context.getException();

    //logJobException(asyncJobId, exceptionThrown);

    System.debug(asyncJobId);

    System.debug(exceptionThrown);

    }

    }

    }

    Then there is the Unit Test. To test this behaviour I need to have a Queueable job that throws and unhandled exception. But when I do that, the test fails as a whole. Of course in real life the job also failed, so it's no different. I was just wondering how to structure the test properly and/or elegantly.

    I was trying to mock out or deserialise the FinalizerContext class to call execute directly, but that's not allowed.

    @IsTest

    private class FinalizerLoggerTest {

    private class AsyncClassWithFinalizer implements Queueable {

    public void execute(QueueableContext context) {

    System.attachFinalizer(new FinalizerLogger());

    Integer i = 1 / 0; //cause exception

    }

    }

    @IsTest

    static void testFinalizerLoger() {

    Id jobId = System.enqueueJob(new AsyncClassWithFinalizer());

    Test.startTest();

    // have Queueable execute by StopTest

    Test.stopTest();

    //assert logs

    }

    }

    The more I look into this the less on an issue it seems. The Class and test can be restructured to allow for things to be called explicitly in tests to ensure coverage. Not 100% real scenario, but we can trust that failed job will produce an exception in the Finalizer.

     

    I followed this up really out of curiosity and to make sure I've not missed something. So thanks very much for taking time to respond.

0/9000

@Chris Peterson

I'm having issues deploying classes that implement the Finalizer interface. All my tests are passing when I run them in my sandbox but when I try to validate a deployment to another sandbox via changesets or sfdx using force:mdapi:deploy (preferred) some of the tests fail. Additionally, when I am trying to validate the changes it will rerun the validation process multiple times.

 

For example, I build a package containing my classes with sfdx force:source:convert then I try to validate it against another sandbox by running sfdx force:mdapi:deploy -c ...etc. I'll see the command complete in my terminal and in the sandbox but once I refresh the deployment status page in the sandbox it's restarted the validation as if it never completed. It will do this 2-5 times before finishing with failed tests even though the tests pass when the classes are deployed to the new sandbox and ran in the developer console or from vs code. 

 

Ever seen anything like this before? I haven't and don't really know where to go from here.

21 comments
  1. Jun 11, 2021, 8:42 PM
    @Jonathan Flatt Our assessment is that, it should not happen in production either (fix was deployed to production on 06/02). The old case (40572686) is closed. Can you please file a new case at the earliest with error details, steps to reproduce, and your orgId? Can you please give us permission to login and also to export sandom? Please share the case # with me so I can expedite the investigation and fix (if a bug exists).
0/9000

G'day folks!  I wanted to make this group aware that @Chris Peterson has graciously agreed to speak to the Richmond, VA Salesforce Developer Group about #TransactionFinalizers on May 10th.  The meet up will be virtual, so we would like to extend the invitation to whomever would like to join us.

 

For more details and registration information, please see the event link below.

"Transaction Finalizers" -- A Fireside Chat with Chris Peterson

3 comments
0/9000

@Chris Peterson I was wondering if you could provide some more details around the serialization/deserialization of the Finalizer object between transactions. My Finalizer object contains an instance of another class with members (Integer, List<String>, etc.). Based on the debug logs, it seems that the class is getting instantiated, but the state that it had before the serialization is not re-created in the Finalizer transaction. 

 

I just would like to understand the boundaries that I have to work in better before I make any design changes. 

 

Thanks! 

12 comments
0/9000

This has huge potential.

 

I see that the focus is really on async context right now - but the possibilities with something like this for sync transactions are really exciting. E.g. have code react to a governor limit exception (such as undoing an API request made during the transaction) - or have much more elegant logic around ensuring transaction rollbacks in case of exceptions with the option of further DML.

 

There are many times working on the platform I'd have killed for something like this - really excited to see where this goes.

3 comments
  1. Jul 23, 2020, 9:08 PM
    @Charles Troster

    hear you. This is one where slow and steady wins the race, since for sync requests we're talking about potentially doubling the resource consumption allowed per request, AND we need special handling in each use case to make sure it works as advertised.

    This will be a long journey, but that's where I plan to take finalizers, albeit starting with async since it has the least material concern about resource contention.

0/9000

When chaining queueable jobs, for developer and trial orgs there is a chain limit of 5. From testing it appears that if I enqueue the next job in the finalizer that chain limit isn't enforced anymore. Is that something that will remain or will there be the same chain limit applied when using finalizers for GA?

5 comments
  1. Jul 23, 2020, 9:06 PM
    @Mike Wannamaker hear you on that. I want to get finalizers to production-usable before we try and tackle that, but I will say I'm not satisfied with the current test support for any non-trivial async processing needs.
0/9000

First, I think this has great potential. Really looking forward to what the actual release implementation looks like.

 

But I am curious why the finalizer (as an interface) has to be attached via System.attachFinalizer with an implementation of a separate class. I guess I was expecting that, as an interface, I could simply implement the methods from the interface directly onto my queueable class: "public SomeQueuableClass implements Database.AllowsCallouts, Queueable, Finalizer"

 

Any insight you could share would be great!

 

Thank you and keep up the great work releasing Apex improvements!

1 comment
  1. Jun 18, 2020, 10:56 PM
    We changed this - finalizer can now be implemented on Queueable jobs in Summer. It made retry semantics overly complicated and there wasn't a good enough justification to keep the constraint.
0/9000