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()andLimits.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()andTest.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.








Leave a Reply