Salesforce Development: Apex

Salesforce Developer Tutorial – How to Mass Delete Records using Apex

4 min read

Subscribe on YouTube

A Quick Introduction

Before we get into this one, I want to introduce Daniel, who put this tutorial together and is the newest content creator for Coding With The Force.

When I started this channel the only thing I had in mind was getting the highest quality Salesforce development content out to you all for free. Thanks to Daniel and a few other creators, we’re going to be able to do a lot more of that than ever before. So thank you, genuinely.

If you’d like to become a content creator here too, reach out and hopefully we’ll get to work together.

Right. Over to the actual content.


Why This Is Useful

Here’s the situation that prompted this. At work we had far too many old records hanging around the org that nobody needed any more.

Interestingly, data storage wasn’t the problem. What we ran into were selective query issues, which start to bite once you have millions of records on an object. Queries that used to be instant start timing out, and reports get slow.

That sent us looking for the best way to keep those record counts under a sensible threshold going forward, given we accumulate more every single day.

Important caveat first: if this is a one-off cleanup, you do not need Apex. Use Data Loader’s bulk delete and you’re done in twenty minutes. Go do that instead.

What follows is for when you need to continuously delete records matching some criteria, on an ongoing basis.


Option 1: Batch Apex

This is the right tool for genuinely large volumes, because Batch Apex processes records in chunks, each with its own fresh governor limits.

Apex
public class OldCaseDeleteBatch implements Database.Batchable<SObject>
{
    public Database.QueryLocator start(Database.BatchableContext bc)
    {
        return Database.getQueryLocator([
            SELECT Id
            FROM Case
            WHERE IsClosed = true
            AND ClosedDate < LAST_N_YEARS:2
        ]);
    }

    public void execute(Database.BatchableContext bc, List<Case> scope)
    {
        Database.delete(scope, false);
    }

    public void finish(Database.BatchableContext bc)
    {
        System.debug('Old case cleanup complete');
    }
}

Run it:

Apex
Database.executeBatch(new OldCaseDeleteBatch(), 200);

Two deliberate choices in there.

Database.getQueryLocator can handle up to 50 million records, far past the normal 50,000 query limit, because it streams rather than loading everything into memory.

Database.delete(scope, false) with allOrNone set to false means one record that can’t be deleted (usually because of a lookup relationship) doesn’t kill the whole chunk. Given you’re processing millions of records, that matters.


Option 2: Queueable Apex

For smaller, more targeted deletions, a Queueable is lighter weight and can chain itself:

Apex
public class OldCaseDeleteQueueable implements Queueable
{
    public void execute(QueueableContext context)
    {
        List<Case> casesToDelete = [
            SELECT Id
            FROM Case
            WHERE IsClosed = true
            AND ClosedDate < LAST_N_YEARS:2
            LIMIT 10000
        ];

        if (casesToDelete.isEmpty())
        {
            return;
        }

        Database.delete(casesToDelete, false);

        //More to do? Queue another round.
        if (casesToDelete.size() == 10000 && !Test.isRunningTest())
        {
            System.enqueueJob(new OldCaseDeleteQueueable());
        }
    }
}

That chaining at the end is the trick. Each job deletes a batch, then queues another if there’s more work, and each new job gets fresh limits.

Note the Test.isRunningTest() guard. You cannot chain a Queueable in a test context, so without it your test throws. That’s a well known trap and it’s easy to forget.


Which One?

Batch for millions of records, or when you want the built-in job monitoring and per-chunk error handling.

Queueable for smaller volumes, or when you want to chain other work after the delete finishes.

For a recurring cleanup, wrap either in a Schedulable and run it nightly:

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

//Every night at 2am
System.schedule('Nightly Cleanup', '0 0 2 * * ?', new NightlyCleanupSchedule());

Please Be Careful With This One

This post is about permanently deleting production data, so a few things worth saying plainly.

Test the query first. Run the SELECT on its own and check the count before you ever wire it to a delete. Then run it again. A wrong WHERE clause here is a very bad day.

Run it in a sandbox first. Every time. No exceptions.

Records go to the Recycle Bin, where they sit for 15 days and count against your storage. If you want the space back immediately, use Database.emptyRecycleBin(). If you want a safety window, don’t.

Cascade deletes are real. Deleting a parent deletes children in master-detail relationships. Know your data model before you start.

Deletes fire triggers. Your before delete and after delete triggers will run, on every record. If they’re heavy, that’s now part of your batch’s CPU budget.

Consider archiving instead. If the requirement is really “get these out of the way” rather than “destroy these forever”, Big Objects let you keep the data without it counting against normal storage or slowing your queries down.

Thanks again to Daniel for putting this one together.


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