Fix Guides
How to Fix Salesforce Apex Governor Limits
Step-by-step fix guide with AI-powered diagnosis from BuildForce.
Apex governor limit errors (TOO_MANY_SOQL_QUERIES, TOO_MANY_DML_ROWS, Apex CPU time limit exceeded, heap size limit exceeded) happen when triggers, batch jobs, or scheduled Apex run inefficiently on bulk data. The immediate fix is to bulkify queries (one SOQL for all records in Trigger.new, not one per record), use collections instead of single-record operations, query selectively with WHERE clauses, move expensive work to async (@future, Queueable, Batch Apex), and aggregate DML into single insert/update calls per object type. BuildForce identifies your top governor-limit consumers from debug logs and traces them to the originating class.
Common Limit Errors
- TOO_MANY_SOQL_QUERIES: 101 SOQL queries per transaction. Usually a query inside a for-loop.
- TOO_MANY_DML_ROWS: 10,000 DML rows per transaction. Bulk job processing too many records in one go.
- TOO_MANY_DML_STATEMENTS: 150 DML statements per transaction. Update inside a for-loop.
- Apex CPU time exceeded: 10,000ms sync, 60,000ms async. Computationally heavy logic or recursive triggers.
- Apex heap size exceeded: 6 MB sync, 12 MB async. Holding too many records or large blobs in memory.
Fix Patterns
- 1. Bulkify queries. Replace per-record SOQL with one query that fetches everything needed for the batch, indexed into a Map by Id.
- 2. Aggregate DML. Build a List per object type during iteration; call insert/update once at the end.
- 3. Selective queries. Always include a selective WHERE clause on an indexed field. Non-selective queries hit the query-timeout limit and consume governor budget.
- 4. Move expensive work async. @future for callouts, Queueable for chained logic, Batch Apex for >50K records.
- 5. Guard against recursion. Use a static Set<Id> in a helper class to track which record IDs have already been processed in the current transaction.
- 6. Use Limits class for safety. Check
Limits.getQueries() < Limits.getLimitQueries()before expensive operations in long-running async jobs.
Example: Trigger Bulkification
// BAD — SOQL in a loop
for (Account a : Trigger.new) {
Contact c = [SELECT Id FROM Contact WHERE AccountId = :a.Id LIMIT 1];
// ...
}
// GOOD — one query, then iterate
Set<Id> accountIds = (new Map<Id, Account>(Trigger.new)).keySet();
Map<Id, Contact> contactsByAccount = new Map<Id, Contact>();
for (Contact c : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds]) {
contactsByAccount.put(c.AccountId, c);
}
for (Account a : Trigger.new) {
Contact c = contactsByAccount.get(a.Id);
// ...
}FAQ
What are the most common Apex governor limit errors?
TOO_MANY_SOQL_QUERIES (101 SOQL limit per transaction), TOO_MANY_DML_ROWS (10,000 DML rows per transaction), TOO_MANY_DML_STATEMENTS (150 DML statements per transaction), Apex CPU time limit exceeded (10,000ms synchronous, 60,000ms async), and Apex heap size limit exceeded (6 MB sync, 12 MB async).
How do I bulkify a trigger?
Three patterns: (1) move SOQL out of for-loops — query once, build a Map<Id, Object>, then iterate; (2) collect records into a List and call insert/update once per type; (3) use Database.* methods with allOrNone=false to handle partial failures gracefully. The trigger framework should iterate Trigger.new without making per-record callouts or queries.
When should I move work to async (@future, Queueable, Batch)?
Async lets you reset governor limits and get a fresh set per execution. Use @future for fire-and-forget callouts. Use Queueable for chained jobs with state. Use Batch Apex for processing >50K records with checkpointing every 200 records (configurable). Don't move work async to hide bad code — fix the bulkification first.
What's the difference between CPU time and execution time?
CPU time is the actual processor time your Apex consumes — excluding callouts, queries waiting on the database, and DML wait. Execution time includes everything. The 10,000ms synchronous CPU limit is strict; the real-world execution can be much longer because DB/callout time doesn't count against it.
Can BuildForce identify which class is causing governor limit hits?
Yes. BuildForce parses the Apex debug logs from your monitored sandbox/production org, identifies which trigger/class is hitting which limit, and traces the call path back to the originating record save or scheduled job. We surface the top offenders ranked by frequency.