{"id":3321,"date":"2026-10-08T12:11:22","date_gmt":"2026-10-08T12:11:22","guid":{"rendered":"https:\/\/www.examtopics.biz\/blog\/salesforce-platform-developer-apex-transactions-governor-limits\/"},"modified":"2026-10-21T13:36:56","modified_gmt":"2026-10-21T13:36:56","slug":"salesforce-platform-developer-apex-transactions-governor-limits","status":"publish","type":"post","link":"https:\/\/www.examtopics.biz\/blog\/salesforce-platform-developer-apex-transactions-governor-limits\/","title":{"rendered":"Salesforce Platform Developer: Apex Transactions and Governor Limits"},"content":{"rendered":"<p>Apex code is not evaluated one record at a time in isolation. It runs inside an execution context that can include many records, automation layers, database operations, and shared platform resources. Code that succeeds when a developer tests one Account can fail when a data load updates 200 Accounts, when a Flow adds extra queries, or when another trigger performs additional DML in the same transaction.<\/p>\n<p>For the <a href=\"https:\/\/www.examtopics.biz\/certified-platform-developer\">Salesforce Platform Developer<\/a>, the central design skill is to reason about the whole transaction. Governor limits are not random obstacles; they are resource boundaries that make multitenant execution predictable. Bulk-safe code treats those limits as an architectural constraint from the first line of design rather than as an exception to fix after deployment.<\/p>\n<h2>Think in execution contexts, not individual method calls<\/h2>\n<p>An Apex transaction is a unit of work with a shared set of governor limits and a commit or rollback outcome. A trigger, helper class, validation-related logic, invocable Apex, and other synchronous automation can all contribute work to the same context. Calling another method does not normally reset the limits simply because the code moved into another class.<\/p>\n<p>This is why a utility method that looks inexpensive by itself can be dangerous. If it performs one SOQL query and the caller invokes it 100 times in a loop, the transaction experiences 100 queries. If five automation layers each assume they own the full limit budget, the combined transaction can fail even though every component passed an isolated unit test.<\/p>\n<p>Salesforce is a <a href=\"https:\/\/www.examtopics.biz\/blog\/what-is-multitenancy-and-why-it-matters-in-software-development\/\">multitenant platform<\/a>, so resource controls prevent one tenant\u2019s code from monopolizing shared compute, database, and memory resources. That design context explains why governor limits are fundamental to Apex rather than optional tuning guidance.<\/p>\n<h2>Transaction boundaries define rollback behavior<\/h2>\n<p>Database work inside one transaction is normally committed together. If an unhandled exception causes the transaction to fail, database changes in that transaction are rolled back. This atomicity is useful because it prevents a business operation from being left half-complete, but developers must understand which work actually belongs to the same boundary.<\/p>\n<p>For example, a trigger that inserts related records and then throws an unhandled exception does not leave those earlier inserts permanently committed. A savepoint can be used to establish a known database state and roll back to it deliberately when business logic requires controlled recovery. However, savepoints do not create a new governor-limit budget; they manage database state within the transaction.<\/p>\n<p>Asynchronous work changes the boundary. Enqueuing a Queueable job schedules separate execution later. The current transaction must first complete successfully for that queued work to be useful, and the asynchronous job runs with its own execution context. Designing with that separation is often better than trying to make one synchronous transaction perform every expensive task.<\/p>\n<h2>Bulkification starts with collections<\/h2>\n<p><a href=\"https:\/\/www.examtopics.biz\/blog\/salesforce-platform-developer-trigger-patterns\/\">Triggers<\/a> can receive many records in one invocation. Bulk-safe code therefore operates on collections rather than assuming <code>Trigger.new<\/code> contains one record. The typical pattern is to inspect all incoming records, collect the identifiers or keys needed for related data, perform a small number of queries, build maps for fast lookup, calculate all changes in memory, and then perform DML on collections.<\/p>\n<p>The anti-pattern is easy to recognize: a SOQL query or DML statement inside a loop over trigger records. That structure makes resource use grow directly with record count. A data import or API call can then exhaust the transaction\u2019s query or DML allowance long before the business operation is complete.<\/p>\n<p>Bulkification is not merely moving a query outside the loop. The result set must also be organized so each record can find what it needs without another database round trip. Maps keyed by record ID or business key are common because they turn repeated database access into in-memory lookup.<\/p>\n<h2>Budget SOQL, DML, rows, CPU, and heap together<\/h2>\n<p>Developers often focus on the query-count limit because it is familiar, but transactions are constrained in several dimensions. A design can stay under the SOQL count and still fail because it retrieves too many rows, executes too many DML statements, consumes excessive CPU, creates a large heap, or performs too many callouts.<\/p>\n<p>Current Salesforce training materials, for example, use 150 DML statements per Apex transaction when explaining why bulk DML is required. Exact allocations can vary by execution context and can evolve, so production code should not be written around the assumption that one remembered number is the entire resource model.<\/p>\n<p>Think of the transaction as a budget. Queries consume database round trips and rows. Nested loops and repeated string or collection work consume CPU. Large object graphs consume heap. Logging can also become expensive. A scalable design minimizes total work, not just the metric that happened to fail first.<\/p>\n<h3>Bulk DML changes the shape of resource use<\/h3>\n<p>If 100 Contacts need to be updated, performing one update operation on a list is fundamentally different from issuing 100 individual update statements. Salesforce counts the bulk operation as one DML statement even though it affects many records, subject to row limits and other constraints. The same principle applies to inserts, deletes, and related operations.<\/p>\n<p>Bulk DML also improves error-handling design. When partial success is acceptable, Database methods can provide per-record results so code can identify failures without discarding every successful record. When the business operation must be atomic, standard DML semantics and exceptions can be appropriate. The choice should match the business transaction, not simply the easiest syntax.<\/p>\n<p>Do not split a logically atomic operation into asynchronous fragments solely to escape limits. Moving work to another transaction changes failure behavior, ordering, and user expectations. Asynchronous design should be used because the work can legitimately be decoupled, delayed, retried, or processed at scale.<\/p>\n<h3>Know how external bulk operations create Apex transactions<\/h3>\n<p>Large imports are not necessarily one giant Apex transaction. APIs and platform services can break workloads into chunks, and each chunk can invoke automation in a separate context. Salesforce documents, for example, that Bulk API DML can process chunks of records as separate Apex transactions.<\/p>\n<p>This matters for state assumptions. A static variable used during one transaction does not provide durable memory across every chunk in a large load. A trigger that assumes it will see the entire file at once can produce incomplete aggregation or duplicate side effects. Cross-transaction coordination requires persistent state or an architecture explicitly designed for asynchronous\/batch processing.<\/p>\n<p>It also matters for tests. A test with 200 records can validate bulk behavior in one context, but integration testing should also consider repeated chunks, retries, and the possibility that related records arrive in separate transactions.<\/p>\n<h2>Use asynchronous Apex to create intentional boundaries<\/h2>\n<p>Queueable, Batch, and Scheduled Apex provide different ways to move work out of the synchronous request. Queueable is useful for discrete jobs that can run later and can be chained when ordered follow-up work is required. Batch Apex is designed for very large data sets processed in controlled scopes. Scheduled Apex provides time-based execution.<\/p>\n<p>The benefit is not simply \u201cmore limits.\u201d A separate execution context protects the interactive transaction from long-running work and creates a clearer operational unit that can be monitored and retried. But asynchronous code introduces eventual consistency: the user\u2019s transaction can finish before the background work changes related data.<\/p>\n<p>Document that tradeoff. If a downstream record must exist before the user continues, async processing may be inappropriate. If an enrichment, integration call, notification, or heavy recalculation can happen later, a queue can make the synchronous path more reliable.<\/p>\n<h2>Design queries around selectivity and purpose<\/h2>\n<p>Reducing query count is necessary but not sufficient. A single query that retrieves far more rows or fields than required can consume row limits, heap, and CPU. Query only the fields needed by the business logic, constrain the result set with meaningful filters, and avoid loading large child collections when an aggregate or targeted query will answer the question.<\/p>\n<p>Be careful with queries hidden behind helper methods or property access. A code review should trace database activity through the full call graph. If a method name sounds harmless but issues SOQL each time it runs, callers need to know that cost.<\/p>\n<p>Likewise, cache repeated lookups within the transaction when the data does not need to be re-read. The goal is not clever caching everywhere; it is to avoid paying for the same database information repeatedly during one unit of work.<\/p>\n<h2>Test for scale, mixed automation, and failure<\/h2>\n<p>Apex tests should include more than happy-path single records. Create realistic collections, exercise insert and update paths, verify behavior when related data is missing, and test that the code does not depend on record order. Use bulk inputs large enough to expose queries or DML that accidentally occur inside loops.<\/p>\n<p>Where possible, test orchestration with the declarative automation that coexists with Apex. Order of execution can cause the same record to be evaluated more than once, values to change after an earlier automation step, or additional DML to occur in the same context. Unit tests for a helper class are necessary but do not replace transaction-level behavior tests.<\/p>\n<p>Failure tests are equally valuable. Confirm that errors are surfaced intentionally, partial-success behavior is understood, and asynchronous jobs are enqueued only when appropriate. A design is robust when the team can predict what commits, what rolls back, and what is retried.<\/p>\n<h2>Troubleshoot the transaction, not the last line<\/h2>\n<p>When a governor-limit exception appears, the line that finally crosses the limit may not be the real cause. Review the complete transaction: trigger invocations, Flows, managed-package automation, helper calls, query counts, DML operations, and repeated execution caused by record updates.<\/p>\n<p>Add instrumentation that records meaningful milestones and resource use during diagnostic work, but avoid turning production logging into another source of heap or CPU pressure. Refactor the highest-multiplying pattern first\u2014often a loop-driven query, repeated recalculation, or recursive update\u2014rather than adding one-off guards around the final failure.<\/p>\n<p>Across <a href=\"https:\/\/www.examtopics.biz\/salesforce-exams\">Salesforce development<\/a>, the durable rule is simple: design for collections, understand transaction boundaries, and treat shared limits as part of the architecture. Code that respects those principles is more likely to survive imports, integrations, automation growth, and production data volumes without becoming a sequence of emergency limit fixes.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Apex code is not evaluated one record at a time in isolation. It runs inside an execution context that can include many records, automation layers, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[38],"tags":[],"class_list":["post-3321","post","type-post","status-publish","format-standard","hentry","category-enterprise-platforms"],"_links":{"self":[{"href":"https:\/\/www.examtopics.biz\/blog\/wp-json\/wp\/v2\/posts\/3321","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.examtopics.biz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.examtopics.biz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.examtopics.biz\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.examtopics.biz\/blog\/wp-json\/wp\/v2\/comments?post=3321"}],"version-history":[{"count":2,"href":"https:\/\/www.examtopics.biz\/blog\/wp-json\/wp\/v2\/posts\/3321\/revisions"}],"predecessor-version":[{"id":5862,"href":"https:\/\/www.examtopics.biz\/blog\/wp-json\/wp\/v2\/posts\/3321\/revisions\/5862"}],"wp:attachment":[{"href":"https:\/\/www.examtopics.biz\/blog\/wp-json\/wp\/v2\/media?parent=3321"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.examtopics.biz\/blog\/wp-json\/wp\/v2\/categories?post=3321"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.examtopics.biz\/blog\/wp-json\/wp\/v2\/tags?post=3321"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}