Salesforce provides numerous powerful features to optimize business processes, and one of the most useful tools for developers is the Queueable Apex. It offers more control over asynchronous execution and is often used for long-running tasks that don’t require immediate completion. However, as useful as queueable jobs are, many developers encounter the issue of “Too Many Queueable Jobs”, an error that indicates limitations in how many jobs can be enqueued at once.
In this blog, we will explore this problem in-depth by covering the causes, common errors, and various strategies to handle them effectively. This guide will include practical code examples to demonstrate solutions and best practices. We will also discuss how to optimize your system to prevent hitting the queueable job limit.
Too Many Queueable Jobs in Salesforce
Queueable Jobs in Salesforce
Salesforce’s Queueable Apex enables you to run asynchronous processes with greater flexibility than the traditional future methods or batch Apex. You can chain jobs, pass complex data types like SObjects or custom classes, and easily monitor job progress.

Despite these advantages, the Salesforce platform enforces limits on the number of too many queueable jobs, both globally and per transaction. The system throws an error when these limits are exceeded, and if not properly managed, this can negatively impact the performance of your Salesforce instance.
Discover the Benefits of Queueable Apex in Salesforce
Queueable Apex provides several key benefits that make it ideal for handling long-running or resource-intensive operations:
- Chaining Jobs: You can chain multiple jobs together, so once one job is completed, another starts automatically.
- Handling Complex Data: Unlike future methods, queueable Apex can handle complex types like SObjects, collections, or custom Apex types.
- Monitor Progress: Since jobs are stored as AsyncApexJob records, you can track their progress easily.
- Improved Limits: Queueable jobs allow more governor limits compared to future methods, offering more flexibility in resource management.
- Retry Mechanism: In case a job fails, Salesforce retries it up to a certain limit, ensuring reliability.
Use Cases of Queueable Jobs
- Integrating with third-party APIs may take time to respond.
- Processing large datasets that are too big for synchronous operations.
- Managing long-running calculations or reports.
Core Features of Queueable Jobs
Queueable Apex comes with several features that give it an edge over other asynchronous operations like future methods and batch Apex:
Job Chaining
You can enqueue another job from an existing job, creating a chain of jobs.
public class FirstJob implements Queueable {
public void execute(QueueableContext context) {
System.debug('First Job Executed');
SecondJob nextJob = new SecondJob();
System.enqueueJob(nextJob);
}
}
public class SecondJob implements Queueable {
public void execute(QueueableContext context) {
System.debug('Second Job Executed');
Monitored Execution
Jobs are stored in AsyncApexJob records, allowing you to monitor their execution.
List<AsyncApexJob> jobList = [SELECT Id, Status FROM
AsyncApexJob WHERE JobType = 'Queueable' AND Status = 'Processing'];
Resource Sharing
Queueable Apex shares the governor limits of the calling transaction, unlike future methods which have stricter limits.
Why the ‘Too Many Queueable Jobs’ Error Happens
The error “Too Many Queueable Jobs Added to the Queue” typically occurs when:
- Job Limits Exceeded: Salesforce enforces a limit of 50 jobs per transaction and a global limit of 250,000 jobs per 24 hours. If your system exceeds these limits, the error is thrown.
- Job Chaining in Loops: If a loop enqueues too many queueable jobs without proper control, you will quickly hit the job limit.
- Batch Apex Interaction: When using batch Apex, chaining queueable jobs from within each batch execution can lead to hitting the governor limit.
- Multiple Triggers: When multiple triggers are executed within the same transaction, each trying to enqueue jobs, the limit may be reached faster than anticipated.
- Complex Operations: If your queueable jobs are dependent on heavy operations like API calls, file processing, or large data manipulations, they may queue up too fast.
Common Errors Related to Queueable Jobs
Here are some common errors you might encounter related to Queueable jobs:
Too Many Queueable Jobs Added to the Queue
This occurs when you try to enqueue more than 50 jobs in a single transaction.
Too Many Queueable JobsException: Too many queueable jobs added to the queue
System.LimitException: Too Many DML Statements
This happens when there are too many DML statements within your queueable jobs, exceeding the governor’s limits.
Unable to Enqueue More Jobs
This occurs when the total number of jobs queued exceeds the 250,000 job global limit.
Essential Steps to Avoid the “Too Many Queueable Jobs” Problem
Avoid Enqueuing Jobs in Loops
You should avoid placing System.enqueueJob() calls inside loops. Instead, batch or chunk your operations to minimize job creation.
for (SObject record : records) {
// BAD PRACTICE: Don't enqueue a job in a loop
System.enqueueJob(new MyQueueableJob(record));
}
// GOOD PRACTICE: Process jobs in bulk
System.enqueueJob(new MyQueueableJob(records));
Leverage Future Methods for Simple Use Cases
If your task doesn’t require the advanced features of Queueable jobs, use future methods to avoid overloading the queueable job limit.
Batch Chaining Instead of Queueable Chaining
If you need to perform numerous asynchronous tasks, prefer using Batch Apex with its ability to handle large-scale data and then chain it with a queueable job at the end of the batch.
Job Monitor and Cleanup
Regularly monitor the AsyncApexJob table to ensure no jobs are stuck in the queue.
Governor Limits Awareness
Always be mindful of Salesforce governor limits and how they impact your queueable job logic. Salesforce provides the Limits.getQueueableJobs() method to monitor how close you are to hitting the limit.
Solutions to Handle the Too Many Queueable Jobs in Salesforce
Using Batch Apex for Bulk Data Processing
If you have a scenario where multiple jobs need to be executed in bulk, switching to Batch Apex is a better choice than enqueuing many Queueable jobs.
public class ProcessRecordsBatch implements Database.Batchable<SObject> {
public Database.QueryLocator start(Database.BatchableContext BC) {
return Database.getQueryLocator([SELECT Id FROM Account LIMIT 50000]);
}
public void execute(Database.BatchableContext BC, List<Account> scope) {
for(Account acc : scope) {
acc.Name = 'Processed ' + acc.Name;
}
update scope;
}
public void finish(Database.BatchableContext BC) {
System.enqueueJob(new FinalQueueableJob());
}
}
Job Chaining Control
Only chain jobs when absolutely necessary, and ensure that jobs are chained in a way that prevents hitting the limit. Below is an example of how to chain jobs conditionally.
public class ChainedJob implements Queueable {
public void execute(QueueableContext context) {
// Perform necessary logic
if (Limits.getQueueableJobs() < 50) {
System.enqueueJob(new AnotherJob());
} else {
System.debug('Job limit reached, cannot enqueue another job.');
}
}
Use Future Methods for Simple Tasks
If your task is simple, such as sending an email or updating a few records, use a future method instead.
@future
public static void sendEmail(Set<Id> contactIds) {
List<Contact> contacts = [SELECT Email FROM Contact WHERE Id IN :contactIds];
// Send email logic
}
Breaking Down Operations with Batch Apex
Instead of enqueueing a Queueable job for every 50 records, leverage batch processing for efficiency.
public class AccountBatchJob implements Database.Batchable<SObject> {
public Database.QueryLocator start(Database.BatchableContext context) {
return Database.getQueryLocator('SELECT Id FROM Account');
}
public void execute(Database.BatchableContext context, List<Account> scope) {
for (Account acc : scope) {
acc.Name = 'Updated ' + acc.Name;
}
update scope;
public void finish(Database.BatchableContext context) {
System.enqueueJob(new FinalQueueableJob());
}
}
In this code, the AccountBatchJob processes records in manageable chunks and, upon completion, enqueues the FinalQueueableJob. This ensures that the total number of queueable jobs stays within limits, while still allowing for asynchronous processing.
Examples Of Too Many Queueable Jobs in Salesforce
Basic Queueable Jobs Implementation
Here’s a simple Queueable job that updates some account records:
public class UpdateAccountsJob implements Queueable {
public void execute(QueueableContext context) {
List<Account> accounts = [SELECT Id, Name FROM Account LIMIT 100];
for (Account acc : accounts) {
acc.Name = 'Updated ' + acc.Name;
}
update accounts;
}
Queueable Job with Chaining
This example demonstrates chaining queueable jobs:
public class FirstJob implements Queueable {
public void execute(QueueableContext context) {
System.debug('First Job Executed');
if (Limits.getQueueableJobs() < 50) {
SecondJob nextJob = new SecondJob();
System.enqueueJob(nextJob);
} else {
System.debug('Cannot enqueue second job, limit reached');
}
}
public class SecondJob implements Queueable {
public void execute(QueueableContext context) {
System.debug('Second Job Executed');
}
Handling Queueable Jobs in a Loop
A better approach for enqueuing jobs in a loop is to batch the processing to avoid hitting governor limits:
public class BulkAccountProcessingJob implements Queueable {
private List<Account> accountsToProcess;
public BulkAccountProcessingJob(List<Account> accounts) {
this.accountsToProcess = accounts;
}
public void execute(QueueableContext context) {
for (Account acc : accountsToProcess) {
acc.Name = 'Processed ' + acc.Name;
}
update accountsToProcess;
}
Querying Async Apex Job to Monitor Jobs
You can query AsyncApexJob to monitor and troubleshoot jobs that are stuck or failed.
List<AsyncApexJob> jobs = [SELECT Id, Status, JobType, NumberOfErrors FROM AsyncApexJob WHERE JobType = 'Queueable' AND Status = 'Failed'];
for (AsyncApexJob job : jobs) {
System.debug('Job Id: ' + job.Id + ' Status: ' + job.Status);
}
Handling Limits with Dynamic Control
You can dynamically check the number of queueable jobs in progress and handle them accordingly:
public class ControlledQueueJob implements Queueable {
public void execute(QueueableContext context) {
if (Limits.getQueueableJobs() < 50) {
System.enqueueJob(new AnotherQueueableJob());
} else {
System.debug('Queueable job limit reached, not enqueuing more jobs.');
}
Using Queueable with Complex Data Types
Queueable jobs can handle complex data types such as custom objects or collections:
public class ComplexDataJob implements Queueable {
public List<MyCustomObject__c> recordsToProcess;
public ComplexDataJob(List<MyCustomObject__c> records) {
this.recordsToProcess = records;
}
public void execute(QueueableContext context) {
for (MyCustomObject__c obj : recordsToProcess) {
obj.Processed__c = true;
}
update recordsToProcess;
}
Combining Batch Apex and Queueable
This example demonstrates combining Batch Apex for bulk processing and a Queueable job for follow-up tasks:
public class AccountBatch implements Database.Batchable<SObject> {
public Database.QueryLocator start(Database.BatchableContext context) {
return Database.getQueryLocator('SELECT Id FROM Account WHERE Name != \'Processed\'');
}
public void execute(Database.BatchableContext context, List<Account> scope) {
for (Account acc : scope) {
acc.Name = 'Processed';
}
update scope;
}
public void finish(Database.BatchableContext context) {
System.enqueueJob(new PostProcessingJob());
}
public class PostProcessingJob implements Queueable {
public void execute(QueueableContext context) {
// Perform post-batch operations here
System.debug('Post-batch processing complete');
}
Error Handling in Queueable Jobs
Adding error-handling logic within Queueable jobs can help manage unexpected failures:
public class SafeQueueableJob implements Queueable {
public void execute(QueueableContext context) {
try {
// Perform some operation that might throw an exception
List<Account> accounts = [SELECT Id, Name FROM Account LIMIT 100];
for (Account acc : accounts) {
acc.Name = 'Updated ' + acc.Name;
}
update accounts;
catch (Exception ex) {
System.debug('Error: ' + ex.getMessage());
}
Chaining Jobs with Conditional Logic
You can chain jobs conditionally, based on the outcome of the previous job:
public class ConditionalQueueJob implements Queueable {
public Boolean conditionMet;
public ConditionalQueueJob(Boolean condition) {
this.conditionMet = condition;
}
public void execute(QueueableContext context) {
if (conditionMet) {
System.enqueueJob(new FollowUpJob());
} else {
System.debug('Condition not met, not enqueuing follow-up job');
}
Checking Governor Limits Before Enqueuing
You can check various governor limits before enqueuing more jobs to avoid reaching the limit:
public class GovernorLimitCheckJob implements Queueable {
public void execute(QueueableContext context) {
if (Limits.getDmlStatements() < Limits.getLimitDmlStatements() && Limits.getQueueableJobs() < 50) {
System.enqueueJob(new NextJob());
} else {
System.debug('Governor limit reached, cannot enqueue more jobs.');
}