Governor Limit Errors and Solutions

Too many SOQL queries

Error Message: LimitException: Too many SOQL queries: 101 out of 100

Common Causes:

Solutions:

Solution 1: Bulkify Queries

Before: SOQL in loop

for (Contact contact : contacts) {
    Account account = [SELECT Name FROM Account WHERE Id = :contact.AccountId];
    // Process - 1 query per iteration
}

After: Bulkified

Set<Id> accountIds = new Set<Id>();
for (Contact contact : contacts) {
    accountIds.add(contact.AccountId);
}

Map<Id, Account> accountMap = new Map<Id, Account>([
    SELECT Id, Name 
    FROM Account 
    WHERE Id IN :accountIds
    WITH SECURITY_ENFORCED
]); // Single query for all accounts

for (Contact contact : contacts) {
    Account account = accountMap.get(contact.AccountId);
    // Process
}

Solution 2: Use Relationship Queries

Before: Separate queries

List<Contact> contacts = [SELECT Id, AccountId FROM Contact];
Set<Id> accountIds = new Set<Id>();
for (Contact c : contacts) {
    accountIds.add(c.AccountId);
}
List<Account> accounts = [SELECT Id, Name FROM Account WHERE Id IN :accountIds];

After: Relationship query

List<Contact> contacts = [
    SELECT Id, AccountId, Account.Name 
    FROM Contact 
    WHERE AccountId != null
    WITH SECURITY_ENFORCED
]; // Single query with related data

Prevention:

Related Patterns: Governor Limits


Too many DML statements

Error Message: LimitException: Too many DML statements: 151 out of 150

Common Causes:

Solutions:

Solution 1: Bulkify DML

Before: DML in loop

for (Contact contact : contacts) {
    contact.Email = 'updated@example.com';
    update contact; // 1 DML per iteration
}

After: Bulkified

for (Contact contact : contacts) {
    contact.Email = 'updated@example.com';
}
update contacts; // Single DML for all records

Solution 2: Batch Processing

public class ContactUpdateBatch implements Database.Batchable<SObject> {
    public Database.QueryLocator start(Database.BatchableContext context) {
        return Database.getQueryLocator('SELECT Id FROM Contact');
    }
    
    public void execute(Database.BatchableContext context, List<Contact> scope) {
        // Process in batches of 200
        for (Contact c : scope) {
            c.Email = 'updated@example.com';
        }
        update scope; // DML per batch
    }
    
    public void finish(Database.BatchableContext context) {
        // Cleanup
    }
}

Prevention:

Related Patterns: Governor Limits


Too many callouts

Error Message: LimitException: Too many callouts: 101 out of 100

Common Causes:

Solutions:

Solution 1: Use @future or Queueable

Before: Callout in loop

for (Contact contact : contacts) {
    HttpResponse response = makeHttpCallout(contact.Id); // Callout in loop
}

After: Batch callouts

Set<Id> contactIds = new Set<Id>();
for (Contact contact : contacts) {
    contactIds.add(contact.Id);
}

// Process callouts in batches
processCalloutsAsync(contactIds);

@future(callout=true)
public static void processCalloutsAsync(Set<Id> contactIds) {
    List<Id> idList = new List<Id>(contactIds);
    // Process in batches of 100 (callout limit)
    for (Integer i = 0; i < idList.size(); i += 100) {
        List<Id> batch = new List<Id>();
        for (Integer j = i; j < Math.min(i + 100, idList.size()); j++) {
            batch.add(idList[j]);
        }
        makeBatchCallout(batch);
    }
}

Prevention:

Related Patterns: Apex Patterns


CPU time limit exceeded

Error Message: LimitException: Apex CPU time limit exceeded

Common Causes:

Solutions:

Solution 1: Optimize Algorithms

Before: Inefficient nested loops

for (Contact contact : contacts) {
    for (Account account : accounts) {
        if (contact.AccountId == account.Id) {
            // Process - O(n*m) complexity
        }
    }
}

After: Optimized with Map

Map<Id, Account> accountMap = new Map<Id, Account>(accounts);
for (Contact contact : contacts) {
    Account account = accountMap.get(contact.AccountId);
    if (account != null) {
        // Process - O(n) complexity
    }
}

Solution 2: Use Batch Processing

// For large datasets, use batch processing
public class LargeDataProcessor implements Database.Batchable<SObject> {
    public Database.QueryLocator start(Database.BatchableContext context) {
        return Database.getQueryLocator('SELECT Id FROM Contact');
    }
    
    public void execute(Database.BatchableContext context, List<Contact> scope) {
        // Process in smaller batches
        processBatch(scope);
    }
    
    public void finish(Database.BatchableContext context) {
        // Cleanup
    }
}

Prevention:

Related Patterns: Apex Patterns


Heap size limit exceeded

Error Message: LimitException: Apex heap size too large: XXXX

Common Causes:

Solutions:

Solution 1: Process in Smaller Batches

Before: Loading all data

List<Contact> allContacts = [SELECT Id, Name, Email, Phone, MailingAddress FROM Contact];
// Process all at once - may exceed heap

After: Process in batches

Integer batchSize = 200;
Integer offset = 0;
Boolean hasMore = true;

while (hasMore) {
    List<Contact> batch = [
        SELECT Id, Name, Email 
        FROM Contact 
        LIMIT :batchSize 
        OFFSET :offset
        WITH SECURITY_ENFORCED
    ];
    
    if (batch.isEmpty()) {
        hasMore = false;
    } else {
        processBatch(batch);
        offset += batchSize;
    }
}

Solution 2: Select Only Needed Fields

Before: Selecting all fields

List<Contact> contacts = [SELECT FIELDS(ALL) FROM Contact LIMIT 200];
// Loads all fields - large heap usage

After: Select only needed fields

List<Contact> contacts = [
    SELECT Id, Name, Email 
    FROM Contact 
    LIMIT 200
    WITH SECURITY_ENFORCED
]; // Minimal fields - smaller heap

Prevention:

Related Patterns: SOQL Patterns


Too many future calls

Error Message: LimitException: Too many future calls: 51 out of 50

Common Causes:

Solutions:

Solution 1: Batch Future Calls

Before: Future in loop

for (Contact contact : contacts) {
    processAsync(contact.Id); // Future call in loop
}

@future
public static void processAsync(Id contactId) {
    // Process
}

After: Batch future call

Set<Id> contactIds = new Set<Id>();
for (Contact contact : contacts) {
    contactIds.add(contact.Id);
}
processBatchAsync(contactIds); // Single future call

@future
public static void processBatchAsync(Set<Id> contactIds) {
    List<Contact> contacts = [SELECT Id FROM Contact WHERE Id IN :contactIds];
    // Process all in one future call
}

Solution 2: Use Queueable Instead

// Queueable can be chained (no 50 limit)
System.enqueueJob(new ProcessQueueable(contactIds));

public class ProcessQueueable implements Queueable {
    private Set<Id> contactIds;
    
    public ProcessQueueable(Set<Id> contactIds) {
        this.contactIds = contactIds;
    }
    
    public void execute(QueueableContext context) {
        // Process
        // Can chain more Queueables if needed
    }
}

Prevention:

Related Patterns: SOQL Patterns - Query optimization