Skip to main content
New tool CRON Expression Builder — preview next run times before you schedule Apex. Open the builder →
A diagram illustrating the flow and processing steps of Queueable Apex in Salesforce development
Apex

Mastering Queueable Apex in Salesforce

The short answer

Queueable Apex is an asynchronous Salesforce processing framework that supports complex data types, job monitoring, and job chaining. It enables developers to move resource-intensive operations to the background to avoid synchronous governor limits.

Key takeaways Pass complex data structures like sObject lists directly into class constructors instead of being limited to primitive parameters. Add a maximum depth check to your chaining logic to prevent infinite loops and avoid hitting platform chaining limits. Separate setup and non-setup object updates into distinct Queueable jobs to prevent mixed DML errors. Implement the Transaction Finalizer interface to handle error recovery and cleanup instead of relying solely on execute method try-catch blocks. Wrap enqueued jobs between Test.startTest() and Test.stopTest() in test classes so the job executes synchronously before assertions.

If you're hitting limits in your synchronous code, Queueable Apex is usually the first tool I recommend. I've spent years fixing performance issues in messy orgs, and honestly, this feature is often the middle ground developers forget about. It's not as clunky as Batch Apex, but it's way more powerful than a simple @future method.

So why does this matter? Well, we've all been in that spot where a trigger starts failing because of a 101 SOQL limit or a callout takes too long. You need to offload that work. Deciding between Asynchronous Apex in Salesforce types can be tricky, but Queueable is often the sweet spot for modern development.

Why I prefer Queueable Apex over future methods

Look, I used to use @future for everything back in the day. But it has some big flaws. You can't pass complex objects like lists of sObjects or custom classes - you're stuck with primitives. Queueable Apex fixes that. It lets you pass actual objects into the constructor, which makes your life so much easier when you're dealing with complex logic.

Another huge win is monitoring. When you fire off a Queueable job, you get a Job ID back immediately. You can actually see it in the Apex Jobs queue and track if it succeeded or failed. With @future? You're basically just hoping for the best. Plus, you can chain jobs together, which is critical when managing Salesforce large data volumes and you need things to happen in a specific order.

A simple implementation that actually works

Here's a quick look at how I usually structure these classes. This example just updates some Account descriptions in the background, but the pattern is what matters. Note how we pass the data through the constructor.

public class AccountUpdateQueueable implements Queueable {
    private List<Account> accountsToUpdate;
    private String updateReason;
    
    public AccountUpdateQueueable(List<Account> accounts, String reason) {
        this.accountsToUpdate = accounts;
        this.updateReason = reason;
    }
    
    public void execute(QueueableContext context) {
        try {
            for (Account acc : accountsToUpdate) {
                acc.Description = 'Updated: ' + updateReason + ' on ' + DateTime.now();
            }
            if (!accountsToUpdate.isEmpty()) {
                update accountsToUpdate;
            }
        } catch (Exception e) {
            // Log this to a custom object so you don't lose the error
            System.debug('Error in Queueable: ' + e.getMessage());
        }
    }
}

Now, to kick this off, you just need a one-liner. It's as simple as this:

Id jobId = System.enqueueJob(new AccountUpdateQueueable(myAccounts, 'Bulk Update'));

Best practices for Queueable Apex in the wild

I've seen teams get into trouble by chaining jobs infinitely. Salesforce has limits on this for a reason. In a production org, you can chain a long sequence of jobs, but in a Developer Edition or trial org, the limit is much lower. Always put a "max depth" check in your code if you're chaining, or you'll eventually hit a wall.

Pro tip: Use the Transaction Finalizer interface if you need to handle errors or clean up after a job. It's much more reliable than trying to wrap everything in a try-catch block inside the execute method.

Here are a few things I always keep in mind when I'm building with Queueable Apex:

  • Watch your limits: Use Limits.getDmlStatements() and Limits.getQueries() to make sure you aren't getting too close to the edge.
  • Bulkify everything: Don't just pass one record if you can pass fifty. It's much more efficient for the platform.
  • Avoid Mixed DML: If you're updating a User (setup object) and an Account (non-setup object), do it in separate jobs to avoid that annoying "MIXED_DML_OPERATION" error.
  • Test properly: You have to use Test.startTest() and Test.stopTest(). The job won't actually run in your test until you hit that stop call.

One thing that trips people up is the async limit. I've written before about how to stay under asynchronous limits, and it's worth a read if you're planning on running thousands of these jobs a day. You don't want to be the person who shuts down the org's automation because you blew through the 24-hour limit.

Key Takeaways

  • Queueable Apex handles complex data types like Lists and Sets, unlike @future methods.
  • You get a Job ID back for every job, making it easy to track in the UI.
  • Chaining allows for sequential processing, but requires guardrails to avoid infinite loops.
  • Always wrap your enqueued jobs in Test.stopTest() to ensure they run during unit tests.

At the end of the day, Queueable Apex is about making your app feel faster for the user. Nobody wants to wait five seconds for a page to save while your code talks to an external API or updates 200 related records. Move that logic to the background. Your users will thank you, and your code will be a lot more resilient because of it.

Frequently asked questions

Why use Queueable Apex instead of future methods?

Queueable Apex supports complex data types like sObjects and custom classes through class constructors, provides a trackable Job ID, and allows sequential job chaining.

How do you test Queueable Apex?

Enqueue the job between Test.startTest() and Test.stopTest() in your test method. The asynchronous job will run synchronously when Test.stopTest() executes, allowing you to query and assert the results.

How do you avoid mixed DML errors in Salesforce?

Split operations on setup objects (such as User) and non-setup objects (such as Account) into separate asynchronous jobs.

How do you handle errors in Queueable Apex?

Use the Transaction Finalizer interface to handle errors and perform post-execution cleanup, which offers a more reliable recovery pattern than wrapping logic in try-catch blocks.

Newsletter

One email every Tuesday

New guides, tool updates, and the release-note changes that break things.

No spam. Unsubscribe in one click.

Comments

Loading comments...

Leave a Comment