Asynchronous Apex in Salesforce – When to Use Each Type

How to use Asynchronous Apex to keep your Salesforce org running fast

We’ve all been there: you’re writing a trigger, and suddenly you hit a governor limit because you’re trying to do too much at once. That’s where Asynchronous Apex comes in to save your life. It’s basically the way we tell Salesforce, “Hey, I need this done, but it doesn’t have to happen right this second.”

If you’re new to the platform or just trying to figure out what Apex actually is, think of it like this: synchronous code is like standing in line at a coffee shop waiting for your latte. Asynchronous is like placing a mobile order and walking away to do other stuff while they make it. You get your coffee eventually, and you didn’t waste ten minutes staring at the back of someone’s head.

Why we use Asynchronous Apex in real projects

I’ve seen plenty of teams try to cram everything into a single transaction. It usually ends in “CPU time limit exceeded” or “Too many SOQL queries” errors. But here’s the thing: Asynchronous Apex isn’t just about avoiding errors. It’s about making the app feel snappy for the user. Nobody wants to wait five seconds for a record to save because a callout is happening in the background.

In my experience, you’ll mostly use these patterns when you need to:

  • Process way more records than a standard trigger can handle.
  • Talk to external APIs (callouts) that might take a few seconds to respond.
  • Run maintenance jobs at 2:00 AM when nobody is logged in.
  • Chain tasks together so Step B only starts after Step A finishes.
A realistic Salesforce administrative dashboard showing a list of asynchronous background jobs and their processing statuses.
A realistic Salesforce administrative dashboard showing a list of asynchronous background jobs and their processing statuses.

Breaking down the common patterns

Salesforce gives us a few different tools for this. Honestly, most teams get this wrong by just using @future for everything. Let’s look at what actually works best in the field.

1. Future methods

These are the old-school way of doing things. You just add an @future annotation to a static method and you’re good to go. They’re great for simple “fire and forget” tasks, like updating a field on a User record after an Account changes.

But they have some annoying downsides. You can’t pass complex objects (like a List of Accounts) into them – you’re stuck with primitive types like IDs or Strings. Also, you can’t track them very easily in the UI. I usually stick to these only for the simplest tasks.


public class MyFutureClass {
  @future(callout=true)
  public static void syncWithExternalSystem(Set<Id> accountIds) {
    // simple logic here
  }
}

2. Queueable Apex

This is probably the most overlooked feature for developers moving up from junior roles. Queueable is like a better version of future methods. You can pass in complex objects, and it returns a Job ID so you can actually see if it succeeded. Plus, you can chain jobs together. If you’re deciding when to use code over Flow, a complex Queueable is often the answer for mid-sized logic.


public class UpdateContactsQueueable implements System.Queueable {
  private List<Contact> contactsToUpdate;
  public UpdateContactsQueueable(List<Contact> contacts) {
    this.contactsToUpdate = contacts;
  }
  public void execute(System.QueueableContext context) {
    // logic goes here
    update contactsToUpdate;
  }
}
// To run it:
System.enqueueJob(new UpdateContactsQueueable(myList));

3. Batch Apex

When you have millions of records, Batch Apex is the only way to fly. It breaks your data into chunks (usually 200 records at a time) and processes them one by one. I’ve used this for massive data migrations where we had to recalculate prices for five million line items. It’s a workhorse, but it does have more boilerplate code than the other options.

Pro Tip: Always use Database.Stateful if you need to count things across the entire batch. Otherwise, your variables reset every time a new chunk starts. I’ve seen that trip up so many developers!

4. Scheduled Apex

This one is pretty self-explanatory. You want something to run every Monday at 8:00 AM? Use the Schedulable interface. One thing I’ve learned the hard way: don’t put heavy logic inside the execute method of a Schedulable class. Instead, have the Schedulable class just start a Batch or a Queueable job. It keeps things much cleaner.

Choosing the right Asynchronous Apex pattern

So how do you actually pick one? It usually comes down to how much data you’re hitting and how complex the logic is. Here’s a quick cheat sheet I use when I’m architecting a solution:

Use CaseBest Pattern
Simple callout from a triggerFuture Method
Chaining multiple async stepsQueueable Apex
Processing 50,000+ recordsBatch Apex
Daily data cleanupScheduled Apex
Decoupling systems with eventsPlatform Events

One thing you can’t ignore is the limits. Salesforce is a multi-tenant environment, so they’re pretty strict about how much “free” processing time you get. If you’re worried about hitting a wall, check out this guide on Asynchronous Apex limits to stay out of trouble.

Key Takeaways

  • Future methods are for simple, fire-and-forget tasks with basic parameters.
  • Queueable Apex is the modern standard for most async needs because of its flexibility.
  • Batch Apex is your best friend for heavy lifting and large data volumes.
  • Scheduled Apex should mostly be used as a “trigger” to start other async jobs.
  • Always monitor your jobs in the Apex Jobs section of Setup to catch errors early.

At the end of the day, Asynchronous Apex is about balance. You want to move the heavy work out of the user’s way without hitting the limits that bring your org to a halt. Start small with a future method if you have to, but don’t be afraid to move to Queueable or Batch once your logic starts getting complicated. It’ll make your code much easier to maintain in the long run.