Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
All errors
Governor limits

System.LimitException: Apex CPU time limit exceeded

Your transaction spent more than 10 seconds (sync) or 60 seconds (async) of CPU time inside Apex code. This counts compute, not waiting — SOQL and DML time don't count, but loops, sorting, parsing, and complex collections do.

Also seen asApex CPU time limit exceeded·CPU time limit exceeded·System.LimitException: Apex CPU time limit

The CPU governor measures wall time spent executing Apex bytecode, not the total elapsed time of the transaction. Time waiting for the database, callouts, or platform events does not count. So the way you fix this is rarely "make queries faster" — it's "do less work in memory."

What burns CPU time

In rough order of how often I've seen them cause this error:

  1. Nested loops over collections. for (a) for (b) where a is Trigger.new and b is a related list. With 200 records each, that's 40,000 iterations, and any non-trivial work inside multiplies fast.
  2. JSON serialise / deserialise on big payloads. JSON.deserializeUntyped on a multi-MB string is brutal.
  3. Repeated String.contains / regex across every record in a long list.
  4. Sorting a large list with a custom Comparable. Apex's sort is O(n log n) but the compareTo callback runs in interpreted code.
  5. describe() calls inside a loop. Each Schema.describeSObjects is fast but not free; cache the result once.

How to find the hot spot

Run it in a sandbox with a debug log set to FINEST on the Profiling category (or use Setup → Trace Flags with the Apex Profiling level cranked up). The CUMULATIVE_LIMIT_USAGE block at the bottom of the log shows total CPU consumed; METHOD_ENTRY / METHOD_EXIT pairs with timestamps let you see which call ate the budget.

Or, instrument by hand:

Long t0 = System.currentTimeMillis();
expensiveBlock();
System.debug('Block took ms=' + (System.currentTimeMillis() - t0)
            + ' cpu=' + Limits.getCpuTime() + '/' + Limits.getLimitCpuTime());

Common fixes by shape

  • Trigger doing nested aggregation? Replace the inner loop with a Map<Id, List<Child>> keyed by parent. Build the map once, then look up.
  • Recursive descent through related records? Cap the depth, or move it into Batch Apex where each chunk gets a fresh 60-second async budget.
  • String/regex hammer? Compile the regex once outside the loop (Pattern.compile), or do a substring check first to short-circuit before the regex.
  • Anything that fits Salesforce's flow + DML + simple queries pattern? Consider replacing Apex with Flow. Flow doesn't share the Apex CPU budget for many operations.

The hidden trap: flow + apex transactions

A record-triggered Flow that calls invocable Apex executes in the same Apex CPU budget as your trigger. So a "lightweight" record-triggered flow can quietly push a heavy Apex chain over the cliff. If you're seeing this error and your transaction includes both, look at the flow first — it might be doing something silly like a "Get Records" with no filter on a 100k-row table.

When you genuinely need more than 10 seconds

You have to shift to async. Queueable Apex, batch Apex, scheduled Apex, and platform-event subscribers all run in the 60-second async budget instead of the 10-second sync one. If your sync user-facing path can't be cut under 10s, decouple it: do the urgent part synchronously and enqueue the rest.

Related dictionary terms