Diagnosing and Resolving Apex CPU Time Limit Exceeded Errors in Salesforce
Step‑by‑step guide to diagnose and fix Apex CPU time limit exceeded errors in Salesforce, with checks, fixes, and escalation rules.
03 Mar 2026, 20:33 UTC

Recognizable Condition
Users encounter an LIMIT_EXCEEDED_FOR_CPU_TIME error in debug logs, email alerts, or the UI when executing Apex triggers, batch Apex, or future methods. The error indicates that a single transaction has consumed more than the 10,000 ms CPU limit imposed by the platform.
Cause / Diagnostic Indicators
| Common Cause | What to Look For in Logs / Behavior |
|---|---|
| Complex trigger logic with many SOQL queries or loops | High cumulative CPU time; numerous SOQL_EXECUTE_BEGIN entries; deep nested loops. |
| Large data volume processed in a single transaction | CPU time spikes when the trigger handles >200 records; LIMIT_USAGE_FOR_NS shows high rows processed. |
| Inefficient recursion or infinite loops | Same trigger class appears repeatedly in the stack; CPU time grows with each recursion. |
| Unoptimized formula fields or roll‑up summaries on parent objects | CPU time consumed during SAVEPOINT or WF_RULE_EVALUATE entries; many formula evaluations. |
| Synchronous callouts within triggers | Added wait time visible as CALLOUT_REQUEST entries; CPU time includes callout duration. |
Ordered Diagnostic Checks
- Enable detailed debug logs for the affected user or profile:
Setup → Monitoring → Debug Logs → New. SetDB=INFO,PROFILING=FINEST,WORKFLOW=INFO. Required permission: Manage Users or Modify All Data. - Reproduce the error** by executing the problematic operation (e.g., inserting a record that triggers the Apex).
- Locate the CPU time** near the top of the log; it appears as
CPU_TIMEfollowed by a millisecond value. Note if it exceeds 10,000 ms. - Identify the Apex unit with highest CPU** – look for the class/trigger name preceding the largest
CPU_TIMEblock. - Inspect SOQL usage** – count
SOQL_EXECUTE_BEGINoccurrences for that unit; high counts suggest bulkification issues. - Review loop structures** – search for nested
FORloops inside the unit; deep nesting often drives CPU consumption. - Check for recursion** – look for a static Boolean flag (or lack thereof) and repeated entries of the same trigger in the stack.
- Verify record count** – add a temporary log line
System.debug('Records processed: '+ [SELECT Count() FROM Object WHERE ...]);or useLimits.getLimitRows()in the code to see how many rows are being processed. - Detect synchronous callouts** – search for
CALLOUT_REQUESTwithin the same transaction; note anyWebServiceCalloutorHttpcalls.
Remediation Tied to Findings
1. Bulkify trigger logic
If the diagnostic shows many SOQL queries inside loops:
- Collect IDs in a
Set<Id>first, then perform a singleSELECTquery. - Use a
Map<Id, SObject>for lookups instead of querying inside the loop. - Example (before):
for (Account acc : Trigger.new) {
for (Contact c : [SELECT Id FROM Contact WHERE AccountId = :acc.Id])
// …
}
Set<Id> accIds = new Set<Id>();
for (Account acc : Trigger.new) accIds.add(acc.Id);
Map<Id, List<Contact>> accToContacts = new Map<Id, List<Contact>>();
for (Contact c : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accIds])
accToContacts.computeIfAbsent(c.AccountId, k => new List<Contact>()).add(c);
for (Account acc : Trigger.new) {
List<Contact> contacts = accToContacts.get(acc.Id);
// process contacts
}
2. Move heavy processing to asynchronous
If the trigger processes large volumes (>200 records) or contains complex logic:
- Refactor to a
QueueableorBatch Apexclass. - Set a batch size of 200 (default) or lower if CPU remains high.
- Example batch class:
global class AccountCleanupBatch implements Database.Batchable<sObject> {
global Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator([SELECT Id FROM Account WHERE LastModifiedDate = LAST_N_DAYS:30]);
}
global void execute(Database.BatchableContext bc, List<Account> scope) {
// bulk‑safe logic here
}
global void finish(Database.BatchableContext bc) {}
}
Database.executeBatch(new AccountCleanupBatch(), 200);3. Add recursion guard
If the same trigger appears repeatedly in the log:
- Insert a private static Boolean flag at the top of the trigger class.
- Example:
trigger AccountTrigger on Account (before insert, before update) {
if (AccountTriggerHandler.isRunning) return;
AccountTriggerHandler.isRunning = true;
try {
// original logic
} finally {
AccountTriggerHandler.isRunning = false;
}
}
public class AccountTriggerHandler {
public static Boolean isRunning = false;
}
4. Simplify formulas / roll‑ups
If CPU time is spent in formula evaluation:
- Reduce the number of fields referenced or replace complex formulas with
CASEorIFthat avoid nested functions. - Consider moving roll‑up summary calculations to a scheduled batch job if they are not needed in real time.
- Required permission: Customize Application (to edit fields).
- Risk: Changing formulas may affect reporting; validate with existing reports and dashboards.
- Move the callout to a
@futuremethod,Queueable, or use Platform Events. - Example:
5. Defer synchronous callouts
If a callout resides inside a trigger:
trigger AccountTrigger on Account (after insert) {
if (Trigger.isAfter && Trigger.isInsert)
AccountCalloutFuture.sendIds(Trigger.newMap.keySet());
}
public class AccountCalloutFuture {
@future(callout=true)
public static void sendIds(Set<Id> accIds) {
// perform callout
}
}
Verification of Fix
- In a full‑copy or developer sandbox that mirrors production data volume, reproduce the original transaction.
- Confirm the debug log shows
CPU_TIMEwell below the limit (e.g., < 6,000 ms) and noLIMIT_EXCEEDED_FOR_CPU_TIMEentry. - Run related unit tests or manual test cases to ensure business logic still produces expected outcomes.
- If the error persists, revisit the diagnostic table and consider additional bulkification or further asynchronous shifting.
Escalation Criteria
Engage Salesforce Support or a senior architect when:
- CPU time remains >10,000 ms after applying all feasible optimizations and the transaction cannot be split further (e.g., unavoidable complex logic).
- The error originates from managed‑package code that you cannot modify.
LIMIT_EXCEEDED_FOR_CPU_TIMEappears consistently across unrelated transactions, hinting at org‑wide limits or a platform issue.- Proposed changes affect critical production functionality and require formal impact assessment or change‑management approval.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.