Skip to main content

Command Palette

Search for a command to run...

Queueable Apex

Updated
8 min readView as Markdown

🌀 Queueable Apex in Salesforce

Queueable Apex is an asynchronous execution method in Salesforce that enables you to run long-running operations—such as external web service callouts or heavy database logic—outside the standard synchronous request. It’s an enhancement over the @future method, offering more control and flexibility.

Jobs submitted via the System.enqueueJob() method are added to the Apex Job Queue and return a job ID, which allows you to monitor job progress using the Apex Jobs page in the UI or by querying the AsyncApexJob object.


📘 Key Concepts

ConceptDescription
🧠 InterfaceClasses must implement the Queueable interface
🛠 MethodRequires an execute(QueueableContext context) method
🔄 ExecutionJobs are queued using System.enqueueJob()
🆔 MonitoringThe method returns a Job ID used to track execution status

💡 Basic Syntax

public class MyQueueableClass implements Queueable {
    public void execute(QueueableContext context) {
        System.debug('Queueable Job ID: ' + context.getJobId());
    }
}

🚀 How to Enqueue a Queueable Job

You can enqueue a job using either of these approaches:

System.enqueueJob(new MyQueueableClass());

Or store the Job ID for tracking:

Id jobId = System.enqueueJob(new MyQueueableClass());
System.debug('Job ID: ' + jobId);

Why Use Queueable Apex Instead of @future?

Feature@futureQueueable
✅ Supports Complex Data Types❌ No✅ Yes
🔁 Job Chaining❌ Not Possible✅ Supported
🆔 Track Job ID❌ No✅ Yes
🧠 Better Code Flexibility❌ Limited✅ High
📦 Ideal for Large/Chained Jobs❌ Not ideal✅ Recommended

🛑 Limitations of Queueable Apex

LimitationDescription
Chaining LimitMaximum of 50 jobs can be chained in a single transaction
🔄 No Nested QueueablesCannot enqueue another Queueable job after reaching chaining limit
📊 Governor Limits ApplyStill subject to asynchronous governor limits (CPU time, SOQL, etc.)
⚠️ From TriggersUse with caution inside triggers with bulk DML to avoid hitting limits

🧪 Example: Queueable Apex with sObject

public class AccountUpdater implements Queueable {
    private Account acc;

    public AccountUpdater(Account acc) {
        this.acc = acc;
    }

    public void execute(QueueableContext context) {
        acc.Name += ' - Updated';
        update acc;
    }
}

// Example Usage
Account a = [SELECT Id, Name FROM Account LIMIT 1];
System.enqueueJob(new AccountUpdater(a));

🆔 How to Retrieve Queueable Job ID

You can get the Job ID in two ways:

  1. Inside the execute() method using QueueableContext:
public void execute(QueueableContext context) {
    System.debug('Job ID: ' + context.getJobId()); // 18-character ID
}
  1. When enqueuing the job using System.enqueueJob():
Id jobId = System.enqueueJob(new MyQueueableClass()); // 15-character ID

📌 Note: The only difference is the length of the returned Job ID (15 vs. 18 characters).


⚙️ Queueable Apex – Real-world Example, Testing, and Chaining

This guide covers a practical example of a Queueable Apex class in Salesforce, including a test class and chaining of multiple Queueable jobs. It also highlights key limitations and best practices to follow while working with asynchronous Apex logic.


🧩 Example: Queueable Apex Class

The following Queueable class searches for an Opportunity named "Test Account Prospect", creates a new parent Account, and links the Opportunity to it.

public class QueueableClassForCreation implements Queueable {

    public void execute(QueueableContext qc) {
        Opportunity opp = [SELECT Id, AccountId FROM Opportunity WHERE Name = 'Test Account Prospect' LIMIT 1];

        Account acc = new Account();
        acc.Name = 'Async Apex Account';
        insert acc;

        opp.AccountId = acc.Id;
        update opp;
    }
}

🧪 Test Class for Queueable Apex

When writing tests for Queueable classes:

  • Always wrap asynchronous calls in Test.startTest() and Test.stopTest().

  • For chained Queueables, use if (!Test.isRunningTest) to prevent recursive enqueuing during test execution.

Test Class Example

@isTest
public class QueueableClassForCreation_Test {

    @testSetup
    public static void setupTestData() {
        Opportunity opp = new Opportunity();
        opp.Name = 'Test Account Prospect';
        opp.CloseDate = System.today();
        opp.StageName = 'Closed Won';
        insert opp;
    }

    @isTest
    public static void testQueueableExecution() {
        Test.startTest();
        System.enqueueJob(new QueueableClassForCreation());
        Test.stopTest();
    }
}

🔗 Chaining Queueable Jobs

Chaining is the practice of enqueuing one Queueable class from another. This is helpful when you want sequential processing across multiple asynchronous jobs.

🔄 How Many Can You Chain?

  • Maximum 5 total jobs: 1 initial + 4 chained

  • Only 1 job can be enqueued from another Queueable

  • 📛 Errors:

    • Too many enqueued:
      System.LimitException: Too many queueable jobs added to the queue: 2

    • Exceeded stack depth:
      System.AsyncException: Maximum stack depth has been reached


🧱 Chaining Example: 5 Queueable Classes

1️⃣ QueueableClass1

public class QueueableClass1 implements Queueable {
    public void execute(QueueableContext qc) {
        System.debug('Before calling Queueable 2');
        if (!Test.isRunningTest()) System.enqueueJob(new QueueableClass2());
        System.debug('After calling Queueable 2');
    }
}

2️⃣ QueueableClass2

public class QueueableClass2 implements Queueable {
    public void execute(QueueableContext qc) {
        System.debug('Before calling Queueable 3');
        if (!Test.isRunningTest()) System.enqueueJob(new QueueableClass3());
        System.debug('After calling Queueable 3');
    }
}

3️⃣ QueueableClass3

public class QueueableClass3 implements Queueable {
    public void execute(QueueableContext qc) {
        System.debug('Before calling Queueable 4');
        if (!Test.isRunningTest()) System.enqueueJob(new QueueableClass4());
        System.debug('After calling Queueable 4');
    }
}

4️⃣ QueueableClass4

public class QueueableClass4 implements Queueable {
    public void execute(QueueableContext qc) {
        System.debug('Before calling Queueable 5');
        if (!Test.isRunningTest()) System.enqueueJob(new QueueableClass5());
        System.debug('After calling Queueable 5');
    }
}

5️⃣ QueueableClass5

public class QueueableClass5 implements Queueable {
    public void execute(QueueableContext qc) {
        System.debug('Inside 5th Queueable');
    }
}

🛡️ Best Practices for Chaining Queueables

Best PracticeReason
✅ Use !Test.isRunningTestPrevents infinite loops or async call issues during test execution
✅ Check chaining limit (max 5)Avoids AsyncException for exceeding stack depth
❌ Don’t enqueue multiple Queueables at onceOnly one System.enqueueJob() allowed per execute() context
✅ Use Test.startTest()/stopTest()Ensures async execution is triggered during unit testing

🧪 Testing Chained Queueable Jobs in Salesforce

Queueable job chaining allows sequential execution of asynchronous jobs. However, testing them requires special care—especially when one job enqueues another.


🧩 Problem: Test Class Fails for Chained Jobs

@isTest
public class QueueableClass1_Test {
    @isTest
    public static void QueueableClass1() {
        Test.startTest();
        System.enqueueJob(new QueueableClass1());
        Test.stopTest(); // ⚠️ Test may fail if QueueableClass1 enqueues another job
    }
}

🔥 Why it fails?

If QueueableClass1 tries to enqueue another job like QueueableClass2, the test context will throw an error such as:

System.AsyncException: Maximum stack depth has been reached

Solution: Use !Test.isRunningTest()

To prevent enqueueing during tests:

public class QueueableClass1 implements Queueable {
    public void execute(QueueableContext qc) {
        System.debug('Inside Queueable 1');

        if (!Test.isRunningTest()) {
            System.enqueueJob(new QueueableClass2());
        }
    }
}

📊 Using Limits.getQueueableJobs()

This method returns the number of Queueable jobs enqueued in the current transaction. Useful for tracking and controlling logic dynamically.

public class SynchronousApexClass {
    public static void callQueueable() {
        System.enqueueJob(new QueueableClass1());
        System.enqueueJob(new QueueableClass2());

        System.debug('Total queueable called are: ' + Limits.getQueueableJobs());
    }
}

🔍 Using System.isQueueable()

This method checks whether the current context is inside a Queueable job.

Example:

public class QueueableClass1 implements Queueable {
    public void execute(QueueableContext qc) {
        SynchronousApexClass.callQueueable();
    }
}
public class SynchronousApexClass {
    public static void callQueueable() {
        if (System.isQueueable()) {
            System.debug('Inside Queueable context');
        } else {
            System.debug('Outside Queueable context');
        }
    }
}

🧪 Key Tips for Queueable Testing

TipPurpose
✅ Use Test.startTest() and Test.stopTest()Ensures async job runs during test
✅ Guard with !Test.isRunningTest()Prevents infinite chaining or async limits during test
✅ Use Limits.getQueueableJobs()Monitor how many jobs are enqueued in the same transaction
🔍 Use System.isQueueable()Control behavior based on async vs sync execution context

🔁 Calling Queueable Apex from a Trigger

Scenario: On Account insert, use a Queueable class to create a child Contact asynchronously.


📦 Trigger + Queueable Design

🧩 AccountTrigger.apxt

trigger AccountTrigger on Account (after insert) {
    if (Trigger.isAfter && Trigger.isInsert) {
        AccountTriggerHandler.createChildContact(Trigger.new);
    }
}

🧠 AccountTriggerHandler.apxc

public class AccountTriggerHandler {
    public static void createChildContact(List<Account> accNewList) {
        System.enqueueJob(new AccountTriggerQueueable(accNewList));
    }
}

🚀 AccountTriggerQueueable.apxc

public class AccountTriggerQueueable implements Queueable {

    public List<Account> accNewList;

    public AccountTriggerQueueable(List<Account> accNewList) {
        this.accNewList = accNewList;
    }

    public void execute(QueueableContext qc) {
        List<Contact> conToCreate = new List<Contact>();

        for (Account acc : accNewList) {
            Contact con = new Contact();
            con.LastName = 'New Con Created';
            con.AccountId = acc.Id;
            conToCreate.add(con);
        }

        if (!conToCreate.isEmpty()) {
            insert conToCreate;
        }
    }
}

💡 Why use Queueable here?
Offloads post-insert processing from the trigger, helping with governor limits, bulk processing, and scalability.


🌐 Callouts with Queueable Apex

To perform a HTTP callout, implement both Queueable and Database.AllowsCallouts.

🛠️ CalloutQueueable.apxc

public class CalloutQueueable implements Queueable, Database.AllowsCallouts {

    public void execute(QueueableContext qc) {
        Http h = new Http();
        HttpRequest req = new HttpRequest();
        req.setEndpoint('https://gorest.co.in/public/v2/posts');
        req.setMethod('GET');

        HttpResponse res = h.send(req);

        System.debug('Status Code: ' + res.getStatusCode());
        System.debug('Body: ' + res.getBody());
    }
}

⚠️ Don’t forget to whitelist the endpoint in Remote Site Settings!


🧪 Test Class for Queueable Callout

🧱 CalloutMock.apxc

@isTest
public class CalloutMock implements HttpCalloutMock {
    public HttpResponse respond(HttpRequest req) {
        HttpResponse res = new HttpResponse();
        res.setStatusCode(200);
        res.setBody('Test JSON Body');
        return res;
    }
}

🚦 CalloutQueueable_Test.apxc

@isTest
public class CalloutQueueable_Test {
    @isTest
    public static void calloutQueueable() {
        Test.startTest();
        Test.setMock(HttpCalloutMock.class, new CalloutMock());
        System.enqueueJob(new CalloutQueueable());
        Test.stopTest();
    }
}

📏 Limitations of Queueable Apex

⚠️ Limitation🚫 Description
🔢 Max Jobs per TransactionUp to 50 Queueable jobs per transaction (System.enqueueJob)
🔗 Max Chaining DepthOnly 5 levels allowed (1 parent + 4 chained child jobs)
🧬 Single Child from ParentOnly 1 child Queueable can be enqueued from within another Queueable
🔁 No Nested QueueablesEnqueuing inside another after limit reached results in AsyncException

More from this blog

B

BlackBUC

29 posts