Salesforce shipped quiet but consequential changes to several Apex governor limits across the last few releases. The most-quoted numbers, the ones engineers memorised years ago and have repeated ever since, mostly held steady. What changed was the enforcement around them, and enforcement changes are exactly the kind that break code which had been scraping by unnoticed. This is the briefing we give clients when we audit a long-running org, framed around the patterns that worked in 2023 and quietly fail now.
01 / DELTAWhat actually changed
Heap doubled in synchronous transactions
The synchronous heap limit moved from six megabytes to twelve. This is the most useful change in years. Bulk patterns that previously had to chunk their work to stay under six megabytes can now run in a single pass at twice the volume. We have watched batch jobs collapse from three iterations to one on the strength of this alone.
CPU enforcement tightened
The visible CPU ceiling is still ten thousand milliseconds in synchronous context. What changed is that the platform now measures more accurately. Code that used to squeak under at nine and a half seconds now fails against the same stated limit. Any class that was running close to the line in 2023 needs a fresh look.
Callouts and queueable depth relaxed
Callouts per transaction rose from one hundred to one hundred fifty. Queueable depth in production moved from five to ten. Both relax constraints that previously forced architectural workarounds, and the platform-event escape hatch, while still useful, is no longer always necessary.
New limits arrived
Two additions worth knowing. There is now a cap on the size of an Apex transaction event log when Event Monitoring is enabled, and a cap on cross-namespace Apex calls that matters once an org carries more than a handful of managed packages.
02 / RISKAnti-patterns that used to scrape by
The patterns we now flag as risky in every audit:
- Nested SOQL inside loops, even short ones. The query limit is still one hundred. Loops that were bulk-safe by luck on a small input list now occasionally meet production volumes that break the limit. Pull every query outside the loop.
- Recursive trigger handlers without explicit guards. The platform detects recursion more strictly now. Add a static boolean guard at the top of every handler, set and checked by the trigger.
- Asynchronous chains built on the future annotation. The annotation is not deprecated, but the queueable framework is now strictly better. Migrate when you touch the code.
- Manual pagination through large lists in synchronous context. With heap doubled, the chunking is often unnecessary. Measure before keeping the complexity.
03 / ORDERThe refactor priority list
Working through a legacy codebase, attack in this order. First, the CPU-tight classes: any class whose average CPU in production logs sits above seven thousand milliseconds will start failing intermittently first, so profile it, find the hot loops, and bulkify or move heavy work to a queueable. Second, the heap users: code that chunks because of the old six-megabyte ceiling, much of which can now run in a simpler, faster single pass. Third, the callout chains: classes that hit the old callout limit and worked around it with platform events, many of which can collapse back to inline callouts now that the limit is higher.
The old advice was to write defensively against governor limits. The new advice is to write defensively against your future self. The limits moved. Your assumptions did not.
04 / TESTSTesting against limits, not numbers
Apex test classes do not automatically inherit the new limits; they run with conservative defaults. If you test close to a boundary, assert against the live limit using the Limits methods rather than a hard-coded number. The test then stays correct as the platform evolves, instead of silently encoding a value that was true the day someone wrote it.
Pull the average and peak CPU and heap for the fifty most-invoked classes over the last thirty days. Sort by peak. Anything in the top decile by peak that is also in the top decile by invocation count is your priority list, in order.
05 / STABLEWhat did not change
Two limits to keep in mind because they have been steady for years and are unlikely to move. SOQL rows returned per transaction is still fifty thousand. DML rows per transaction is still ten thousand. If your code hits these regularly, the answer is not to wait for a platform change. The answer is batch Apex or queueable chaining, designed in deliberately.
The platform is generous with the headline numbers and strict with the patterns. Treat the limits as living constraints rather than figures you memorised once, read the current values from the Limits class in your own code, and the next release becomes a non-event rather than an incident.