The Apex Master Class

The Complete Guide to Asynchronous Apex (Ep. 33)

6 min read

Subscribe on YouTube

The Last One

Thirty three episodes. You’ve gone from “what is Salesforce” to writing bulkified triggers and proper SOQL. That’s genuinely a lot, and if you’ve followed along the whole way you should be quietly pleased with yourself.

For the finale: asynchronous Apex. This is how you escape a lot of the limits we’ve spent the entire series carefully tiptoeing around.


What Does Asynchronous Mean?

Everything we’ve written so far is synchronous. The user clicks Save, your code runs, they wait, they get a response. All in one transaction.

Asynchronous means your code gets queued up and runs later, on Salesforce’s schedule. The user doesn’t wait. The work happens in its own transaction.

Two reasons this matters enormously.

1. Higher limits. Async transactions get roughly double most governor limits. 200 SOQL queries instead of 100. 12MB heap instead of 6MB. 60 seconds CPU instead of 10.

2. Callouts after DML. You cannot make a web callout after you’ve done DML in the same transaction. Salesforce won’t let you, because it doesn’t want an uncommitted transaction held open while you wait on some external server. Going async is the standard way round this.

Four flavours to cover: Future, Queueable, Batch and Schedulable.


1. Future Methods

The oldest and simplest. Slap an annotation on a static method and it runs later:

Apex
public class AccountService
{
    @future
    public static void doSomethingLater(Set<Id> accountIds)
    {
        List<Account> accounts = [SELECT Id, Name FROM Account WHERE Id IN :accountIds];
        //...
    }

    //Making a callout? You need this version.
    @future(callout=true)
    public static void callSomeApi(Set<Id> accountIds)
    {
        //HTTP callout here
    }
}

The restrictions are where it gets annoying:

1. Must be static and return void.
2. Parameters can only be primitives or collections of primitives. No SObjects. No custom classes.
3. You can’t call a future method from another future method.
4. No way to track it or chain anything after it.

That second one is why you’ll see Set<Id> passed around constantly instead of List<Account>. You pass Ids and re-query inside. Which is actually the correct thing to do anyway, since the records may have changed by the time it runs.

Honestly? Future methods are mostly legacy at this point. Queueable does everything they do and more. I’m covering them because you will absolutely inherit code full of them, not because you should write new ones.


2. Queueable (Use This One)

Apex
public class AccountProcessor implements Queueable
{
    private List<Account> accounts;

    public AccountProcessor(List<Account> accountsToProcess)
    {
        this.accounts = accountsToProcess;
    }

    public void execute(QueueableContext context)
    {
        for (Account acct : accounts)
        {
            acct.Description = 'Processed asynchronously';
        }
        update accounts;
    }
}

//Kick it off
System.enqueueJob(new AccountProcessor(myAccounts));

Look at what that fixes. It’s a real class with a constructor, so you can pass it whatever you like, including SObjects and your own custom types. Everything you learned about classes applies.

Two more things it gives you:

A job Id you can track. System.enqueueJob() returns an Id you can query against AsyncApexJob to check on it.

Chaining. You can enqueue another job from inside execute():

Apex
public void execute(QueueableContext context)
{
    //do the work...
    System.enqueueJob(new NextJobInTheChain());
}

Which lets you break a big job into sequential steps, each with its own fresh set of limits. Note you can only chain one job per execution, so no forking.

Need callouts? Implement Database.AllowsCallouts as well:

Apex
public class ApiCaller implements Queueable, Database.AllowsCallouts
{
    public void execute(QueueableContext context) { ... }
}

3. Batch Apex

When you need to process more records than any single transaction could handle. Millions, potentially.

Batch Apex splits the work into chunks and runs each chunk as its own transaction with its own fresh limits. That’s the whole trick.

Apex
public class AccountBatch implements Database.Batchable<SObject>
{
    //1. What records are we processing?
    public Database.QueryLocator start(Database.BatchableContext bc)
    {
        return Database.getQueryLocator([SELECT Id, Name FROM Account]);
    }

    //2. Runs once per chunk (200 records by default)
    public void execute(Database.BatchableContext bc, List<Account> scope)
    {
        for (Account acct : scope)
        {
            acct.Description = 'Processed by batch';
        }
        update scope;
    }

    //3. Runs once at the very end
    public void finish(Database.BatchableContext bc)
    {
        System.debug('All done!');
    }
}

//Run it, 200 records at a time
Database.executeBatch(new AccountBatch(), 200);

Three methods: start defines the data, execute processes one chunk, finish tidies up.

Database.getQueryLocator is the important bit. It can handle 50 million records, far past the normal 50,000 limit, because it streams rather than loading everything at once.

Two practical notes:

Batch size matters. Default is 200. If each record is doing heavy work, drop it to 50 or lower. If you’re hitting CPU timeouts in a batch, shrinking the batch size is usually the fix.

Statics reset between chunks. Each chunk is a separate transaction, so static variables don’t carry over. If you need running state across chunks, use instance variables and implement Database.Stateful.


4. Schedulable

For running things on a timer. Nightly cleanups, weekly reports, that sort of thing.

Apex
public class NightlyAccountJob implements Schedulable
{
    public void execute(SchedulableContext context)
    {
        Database.executeBatch(new AccountBatch(), 200);
    }
}

//Every night at 1am. That's a cron expression.
System.schedule('Nightly Account Job', '0 0 1 * * ?', new NightlyAccountJob());

Schedulable kicking off a Batch job is the standard pairing you’ll see everywhere.

One real limitation: the scheduler only goes down to hourly through the UI. If you need something running more often, there’s a trick where the job reschedules itself at the end of its own run. I’ve written up how to build a self-scheduling Apex class, which also saves you from having 1,440 scheduled jobs cluttering up your org.


Which One Do I Use?

Future – don’t, unless you’re maintaining existing code.
Queueable – your default for anything async. Complex parameters, trackable, chainable.
Batch – more records than one transaction can handle.
Schedulable – anything that runs on a timer.

Two limits to keep in mind: you get 50 async jobs queued per transaction, and 5 concurrent batch jobs org-wide. That second one bites in busy orgs, so don’t assume your batch starts the instant you call it.


A Word Of Caution

Async is not a free pass. Some things get genuinely harder:

Errors are invisible. There’s no user staring at a screen to see the failure. If an async job blows up, it fails quietly and you find out from a confused colleague three days later. Log your failures somewhere you’ll actually look. Nebula Logger is excellent for this.

Ordering isn’t guaranteed. Queue two jobs and they may not run in the order you queued them.

Testing needs Test.startTest() and Test.stopTest(). Async work only actually executes at stopTest(). Forget that wrapper and your test passes without ever running the thing you were testing, which is a special kind of frustrating.

So use async when you need it, not because it feels more sophisticated.


That’s The Course

You made it through all thirty three episodes. Seriously, well done.

You started not knowing what Apex was. You now know classes, methods, constructors, collections, access modifiers, loops, triggers, SOQL and asynchronous processing. That is a genuinely employable set of skills, and more importantly you know why things are done the way they are, not just the syntax.

Where to go next, roughly in order:

1. Separation of Concerns and the Apex Common Library – how to structure code that stays maintainable as it grows.
2. SOLID Design Principles – the principles behind good object oriented design.
3. The LWC Master Class – the front end half of the platform.

Thank you for sticking with it. Go build something.

See you in the next series!


Get Coding With The Force Merch!!

We now have a redbubble store setup so you can buy cool Coding With The Force merchandise! Please check it out! Every purchase goes to supporting the blog and YouTube channel.

Get Shirts Here!
Get Cups, Artwork, Coffee Cups, Bags, Masks and more here!


Check Out More Coding With The Force Stuff!

If you liked this post make sure to follow us on all our social media outlets to stay as up to date as possible with everything!

Youtube
Patreon
Github
Facebook
Twitter
Instagram


Salesforce Development Books I Recommend

Advanced Apex Programming
Salesforce Lightning Platform Enterprise Architecture
Mastering Salesforce DevOps

Good Non-SF Specific Development Books:

Clean Code
Clean Architecture