How to Resolve Too Many SOQL Queries Error in Salesforce Apex

If you’re a Salesforce developer, you have probably come across the “Too many SOQL queries: 101” error at least once during development. It’s one of those classic errors that every developer encounters sooner or later in salesforce flow or apex .

This error simply means your code is trying to run too many database queries in a single transaction and Salesforce stops it to maintain system performance.

What is the “Too many SOQL queries: 101” Error?

This System.LimitException signifies that your Apex code has executed more SOQL (Salesforce Object Query Language) queries than the platform allows within a single transaction context.

As we know that Salesforce operates on a multi-tenant architecture, sharing resources among many customers. To ensure fair usage and prevent any single transaction from monopolizing database resources, Salesforce imposes governor limits. One of the most critical limits is the number of database queries allowed.

The specific governor limit is:

  • Total number of SOQL queries issued: 100 (in a synchronous transaction, like a trigger , flow)
  • Total number of SOQL queries issued: 200 (in an asynchronous transaction, like future or Batch Apex)

When your code attempts to execute the 101st query , Salesforce immediately halts the transaction and throws this exception. The “101” in the error message is simply the result of your code exceeding the 100-query limit.

What Causes the “Too many SOQL queries” Error?

While there can be complex scenarios, the primary cause of this error is simple and common:

Cause 1: The Main Culprit – SOQL Queries Inside Loops

Placing a SOQL query inside a “for” or “while” loop is the cardinal sin of Apex development regarding governor limits.

Why is this so bad?
Imagine a trigger that fires when 200 Contact records are updated. If you put a SOQL query inside the loop that iterates over Trigger.New (the list of 200 Contacts), that single query will execute 200 times! This instantly blows past the limit of 100 queries allowed in a synchronous transaction.

Example (Incorrect Code):

// DANGER: SOQL query inside a for loop.
// This trigger tries to find the parent Account for each updated Contact individually.

trigger ContactTrigger on Contact (after update) {
    for (Contact con : Trigger.new) {
        // This query runs ONCE FOR EVERY contact in Trigger.new
        Account parentAcc = [SELECT Id, Name FROM Account WHERE Id = :con.AccountId LIMIT 1]; 
        
        // ... some logic using parentAcc ...
        System.debug('Account Name: ' + parentAcc.Name); 
    }
}


If this trigger fires due to a bulk update of 150 contacts, it will attempt 150 SOQL queries and immediately fail with the “Too many SOQL queries: 101” error.

Cause 2: Inefficient Helper Methods

A more hidden cause is calling a helper method from within a loop, where that helper method contains a SOQL query. This is the same problem, just one step removed.

Example (Incorrect Helper Method):

// IN THE TRIGGER:
trigger ContactTrigger on Contact (after update) {
    for (Contact con : Trigger.new) {
        // This helper method is CALLED inside the loop
        AccountUtility.processParentAccount(con.AccountId);
    }
}

// IN THE 'AccountUtility' HELPER CLASS:
public class AccountUtility {
    public static void processParentAccount(Id accountId) {
        // The SOQL is HIDDEN here, but it's still inside a loop!
        Account parentAcc = [SELECT Id, Name FROM Account WHERE Id = :accountId LIMIT 1];
        // ... logic ...
    }
}


How to Debug the “Too many SOQL queries” Error

Debugging this error is usually straightforward. The debug log tells you exactly where your code is spending its query limit.

  1. Open the Debug Log: Find the debug log for the failed transaction.
  2. Use “Cumulative Limit Usage”: At the bottom of the debug log, you will find the “Cumulative Limit Usage” summary. This will confirm you hit the limit (e.g., Number of SOQL queries: 101 out of 100).
  3. Find the Repeated Query: The easiest way to find the culprit is to search the log file.
    • Press Ctrl+F (or Cmd+F) to search.
    • Search for the text SOQL_EXECUTE_BEGIN.
    • You will see a list of every query that was executed. If you see the exact same query text (or a query that just has a different ID) repeated over and over, you have found your query that is inside a loop.
  4. Use the “Analysis” Perspective: In the Developer Console, open the log and go to Debug > View Log Panels. Select Execution Log and Stack Tree.
    Click on an SOQL_EXECUTE_BEGIN line in the log.
    The Stack Tree will show you the exact method (e.g., processParentAccount) and trigger (ContactTrigger) that called it. This helps you trace the problem, even if it’s in a helper class.

How to Resolve the “Too many SOQL queries” Error

The solution is fundamental to writing efficient Apex: Bulkification. You must refactor your code to ensure SOQL queries are executed outside of loops.

The Bulkification Pattern (Collecting and Mapping)

This pattern is the most important concept in Apex trigger development.

  1. Collect IDs/Data: Before your loop begins, iterate through your trigger context variable (like Trigger.new) and collect the necessary IDs or other data points needed for your query into a Set or List. Using a Set is often preferred for IDs to automatically handle duplicates.
  2. Query Once: Perform a single SOQL query outside the loop, using the collected Set/List in the WHERE clause with the IN operator.
  3. Process Efficiently (Often with a Map): If you need to correlate the queried data back to your original records, store the results of your query in a Map. The keys of the Map are typically the IDs you collected in step 1.
  4. Loop and Use the Map: Now, loop through your original records. Inside the loop, efficiently retrieve the related data you queried earlier directly from the Map using the record’s ID as the key.

Applying the Pattern (Fixing the Contact Trigger Example)

Here is the fix for the buggy trigger shown above.

// CORRECT: Bulkified code with SOQL outside the loop.
trigger ContactTrigger on Contact (after update) {

    // 1. Collect Account IDs before the loop
    Set<Id> accountIds = new Set<Id>();
    for (Contact con : Trigger.new) {
        // Only add if AccountId is not null
        if (con.AccountId != null) {
            accountIds.add(con.AccountId);
        }
    }

    // Check if there are any Account IDs to query
    if (!accountIds.isEmpty()) {
        
        // 2. Query ONCE outside the loop using the Set
        Map<Id, Account> accountMap = new Map<Id, Account>(
            [SELECT Id, Name FROM Account WHERE Id IN :accountIds]
        );

        // 4. Loop through Contacts again, using the Map
        for (Contact con : Trigger.new) {
            // Check if the contact has an Account and if that Account was queried
            if (con.AccountId != null && accountMap.containsKey(con.AccountId)) {
                Account parentAcc = accountMap.get(con.AccountId);
                
                // ... Now perform your logic using parentAcc ...
                System.debug('Processing Contact: ' + con.LastName + ', Account Name: ' + parentAcc.Name); 
            }
        }
    }
}


This revised trigger now executes only one SOQL query, regardless of whether 1 or 200 contacts are updated, easily staying within the governor limit.

A Simple Demo: From Bug to Fix

Let’s use the Developer Console’s Anonymous Window to see this in action.

Scenario: We want to retrieve the Name of the parent Account for a list of specific Contact IDs.

The Buggy Code (SOQL Inside Loop)

// Open Developer Console -> Debug -> Open Execute Anonymous Window
// Run this code. It *might* work for a few IDs, but will fail if the list is large.

// First, get 150 Ids to use in our test
List<Contact> contactsToTest = [SELECT Id FROM Contact LIMIT 150];
List<Id> contactIds = new List<Id>();
for(Contact c : contactsToTest) {
    contactIds.add(c.Id);
}

// Now, start the test that will fail
Long queryCountStart = Limits.getQueries();

try {
    for(Id contactId : contactIds) {
        // SOQL inside the loop!
        Contact c = [SELECT Account.Name FROM Contact WHERE Id = :contactId]; 
        System.debug('Account Name: ' + c.Account.Name);
    }
} catch (System.LimitException e) {
    System.debug('Caught expected error: ' + e.getMessage()); 
    // You'll see the "Too many SOQL queries: 101" error here.
}

Long queryCountEnd = Limits.getQueries();
System.debug('SOQL Queries used: ' + (queryCountEnd - queryCountStart));

How to Resolve Too many SOQL queries 101

How to Resolve Too many SOQL queries 101


The Fixed Code (Bulkified)

// Now run this fixed version. It will succeed and use only ONE query.

List<Contact> contactsToTest = [SELECT Id FROM Contact LIMIT 150];
List<Id> contactIds = new List<Id>();
for(Contact c : contactsToTest) {
    contactIds.add(c.Id);
}

Long queryCountStart = Limits.getQueries();

try {
    // Query ONCE outside the loop
    Map<Id, Contact> contactMap = new Map<Id, Contact>(
        [SELECT AccountId, Account.Name FROM Contact WHERE Id IN :contactIds]
    );

    // Loop through the original IDs and use the Map
    for(Id contactId : contactIds) {
        if(contactMap.containsKey(contactId)) {
            Contact c = contactMap.get(contactId);
             // Use safe navigation operator just in case Account is null
            System.debug('Account Name: ' + c.Account?.Name);
        }
    }

} catch (System.LimitException e) {
    // This should not be hit now
    System.debug('Unexpected error: ' + e.getMessage()); 
}

Long queryCountEnd = Limits.getQueries();
System.debug('SOQL Queries used: ' + (queryCountEnd - queryCountStart)); // Should show 2 (one for setup, one for the test)

Best Practices to Prevent This Error

  • Bulkify Everything: This is the #1 rule. Always assume your code will run on more than one record. Never place SOQL or DML statements inside loops.
  • Follow coding principals for Apex Code in Salesforce Developer’s Guide for avoiding these errors..
  • Leverage Maps for Correlation: Maps are essential for efficiently relating records queried outside a loop back to the records being processed inside the loop.
  • For more information you can also check out Salesforce official article .

Author

  • Salesforce Hours

    Salesforcehour is a platform built on a simple idea: "The best way to grow is to learn together". We request seasoned professionals from across the globe to share their hard-won expertise, giving you the in-depth tutorials and practical insights needed to accelerate your journey. Our mission is to empower you to solve complex challenges and become an invaluable member of the Ohana.


Discover more from Salesforce Hours

Subscribe to get the latest posts sent to your email.

Leave a Reply

Discover more from Salesforce Hours

Subscribe now to keep reading and get access to the full archive.

Continue reading