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
- 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.
How it works
Key per-transaction limits
| Limit | Synchronous | Asynchronous |
|---|---|---|
| SOQL queries issued | 100 | 200 |
| Records retrieved by SOQL | 50,000 | 50,000 |
| SOSL searches | 20 | 20 |
| DML statements | 150 | 150 |
| Records processed by DML | 10,000 | 10,000 |
| CPU time | 10,000 ms | 60,000 ms |
| Heap size | 6 MB | 12 MB |
| Callouts (HTTP or web service) | 100 | 100 |
@future calls | 50 | 0 in batch and future contexts |
System.enqueueJob calls | 50 | 1 |
sendEmail invocations | 10 | 10 |
| Trigger stack depth | 16 | 16 |
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()andgetAll()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
| Problem | Pattern |
|---|---|
| Query per record | Collect Ids in a Set, query once with IN :ids |
| DML per record | Add 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 process | Batch Apex; a SOQL for loop or aggregate query still counts every row toward 50,000 |
| Heap size with large queries | SOQL for loops process records in chunks of 200 |
| Many callouts or long work | Move 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.
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.
LimitExceptioncannot be caught; each batch execute gets fresh limits.
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.