Process Automation and Logic · Topic 5 of 7

Governor Limits in Apex Transactions

Apex governor limits for Platform Developer: SOQL, DML, CPU, heap, callout and async limits, the Limits class, bulk patterns and what happens at a limit.

  • 4 min
In a nutshell
  • 100 SOQL (200 async), 50,000 query rows, 150 DML statements, 10,000 DML rows.
  • CPU 10 seconds sync, 60 seconds async; heap 6 MB sync, 12 MB async.
  • 100 callouts, 50 future calls, 50 queueable jobs, 20 SOSL searches per transaction.

What it is

Governor limits are hard caps the platform enforces per Apex transaction so that one org cannot overload shared resources. When code exceeds a limit, the platform throws System.LimitException, which cannot be caught, and the whole transaction rolls back.

A transaction includes everything that runs from one entry point: triggers, flows, workflow, validation and any Apex they call. They all share one set of limits.

Entry pointDML, page, APITriggers and flowssame transactionShared limitsSOQL, DML, CPUCommitor rollback

How it works

Key per-transaction limits

LimitSynchronousAsynchronous
SOQL queries issued100200
Records retrieved by SOQL50,00050,000
SOSL searches2020
DML statements150150
Records processed by DML10,00010,000
CPU time10,000 ms60,000 ms
Heap size6 MB12 MB
Callouts (HTTP or web service)100100
@future calls500 in batch and future contexts
System.enqueueJob calls501
sendEmail invocations1010
Trigger stack depth1616

Asynchronous means @future, Queueable, Batch Apex execute and scheduled Apex. Each batch execute call gets its own fresh set of limits.

How counting works

insert accounts;     // 1 DML statement, accounts.size() DML rows
List<Account> a = [SELECT Id, (SELECT Id FROM Contacts) FROM Account LIMIT 200];
// 1 SOQL query; the rows retrieved count toward 50,000
  • One DML statement on a list of 200 records uses 1 statement and 200 rows.
  • A query counts once, however many rows it returns.
  • Custom metadata getInstance() and getAll() do not use SOQL queries.
  • Flows' Get Records and Update Records elements count toward the same limits.

The Limits class

System.debug(Limits.getQueries() + ' of ' + Limits.getLimitQueries());
System.debug(Limits.getDmlRows() + ' of ' + Limits.getLimitDmlRows());
System.debug(Limits.getCpuTime() + ' ms CPU used');

Limits.getX() returns usage so far; Limits.getLimitX() returns the limit. The end of each debug log also lists cumulative limit usage.

Patterns that stay inside limits

ProblemPattern
Query per recordCollect Ids in a Set, query once with IN :ids
DML per recordAdd to a List, one DML after the loop
Nested loops over large lists (CPU time)Index one list in a Map<Id, SObject>
More than 50,000 rows to processBatch Apex; a SOQL for loop or aggregate query still counts every row toward 50,000
Heap size with large queriesSOQL for loops process records in chunks of 200
Many callouts or long workMove to Queueable or Batch Apex

Managed packages

Certified managed packages from AgentExchange (formerly AppExchange) get their own separate counts for most per-transaction limits, such as SOQL queries and DML statements. Their usage appears under their namespace in the debug log and does not consume the org's own count.

A worked example

An after update trigger on Account loops over 200 accounts. For each one it queries its Contacts and runs update c; for each of two contacts.

  • Queries: one per account, so the 101st account would fail.
  • DML statements: two per account, so the 76th account needs statements 151 and 152.

DML hits its limit first, at the 76th account. The fix: query all Contacts for all accounts once with WHERE AccountId IN :Trigger.newMap.keySet(), set the field values in memory, and run one update on the list.

Exam tip

For "which limit is hit first" questions, divide the limit by the usage per record for each resource, and the smallest result wins.

How the exam asks about this

  • "How many SOQL queries can a synchronous transaction issue?" 100.
  • "A trigger and a flow in the same save use 90 and 15 queries." The save fails at the 101st query.
  • "Can a try/catch handle a LimitException?" No.

What to remember for the exam

  • 100 SOQL (200 async), 50,000 query rows, 150 DML statements, 10,000 DML rows.
  • CPU 10 seconds sync, 60 seconds async; heap 6 MB sync, 12 MB async.
  • 100 callouts, 50 future calls, 50 queueable jobs, 20 SOSL searches per transaction.
  • Triggers, flows and Apex in one save share one set of limits.
  • LimitException cannot be caught; each batch execute gets fresh limits.
Watch out

Moving a per-record query into an @future method does not fix a bulk problem. Each future call is limited, and 50 calls is the cap.

Common mistakes

Believing limits reset per trigger or per flow. They are per transaction.

Counting rows as statements. update list; is one statement, however many records.

Raising the batch scope to process more. Bigger scopes use more limits in each execute; a query per record breaks sooner.

Check yourself

  • What is the DML row limit in one transaction?

    10,000 records processed by all DML statements combined.

  • A batch job's execute method uses 150 queries. Does the next execute start at 150?

    No. Each execute call has its own fresh set of governor limits.

  • Which exception type does a governor limit throw, and can it be caught?

    System.LimitException, and it cannot be caught.

Topic 5 of 7 in Process Automation and Logic

Test yourself on Process Automation and Logic

The full Salesforce Platform Developer question bank has 645 questions, with timed practice exams and an explanation for every answer.

This study guide is independent and is not affiliated with, endorsed by, or sponsored by Salesforce. Vendor names, certification names and exam codes are trademarks of their respective owners and are used for identification only.