Platform
Salesforce Platform Cache: Org Cache, Session Cache, and the pattern that makes them safe
An Apex method rebuilds the same expensive table on every request, for data that changes once a day. Platform Cache fixes that, but it is best-effort storage that can vanish between transactions. Here is Org vs Session Cache, the capacity you get, the CacheBuilder pattern, and the rule that keeps a cache miss from becoming an outage.
An Apex method calls out to a rate service, parses the response, and builds a 900-row currency table. It does that on every request, for data that changes once a day. The work is real, the result is identical, and the user waits for it every time.
Platform Cache is the Salesforce answer to that waste. It stores data in memory across transactions so Apex stops recomputing what it already knows, without a custom object or Visualforce view state to hold it.
My rule for it is short. Cache data that is expensive to build, read far more often than it changes, and safe to lose. Use Org Cache when every user needs the same copy, Session Cache when the data belongs to one user, and treat neither as your source of truth, because a cache entry can disappear between one transaction and the next.
Org Cache and Session Cache are not the same tool
The split is about who can see the data and how long it lives. Org Cache is org-wide: anything you put there is readable by any user, across sessions and requests. A product catalog, reference tables, the result of an expensive aggregate, all belong here.
Session Cache is scoped to one user’s session. Only that session can read it, and it dies when its time-to-live expires or when the user’s session ends, whichever comes first. A user’s in-progress selections or a per-user computed value go here, never in Org Cache where another user could read them.
That last point is a security line, not a style preference. User-specific or sensitive data in Org Cache is visible org-wide. If the data is about one person, it belongs in their session.
What you get, by edition
Capacity is provisioned by edition, and the numbers decide whether Platform Cache is even an option. Enterprise Edition gets 10 MB by default; Unlimited and Performance get 30 MB; every other edition gets 0 MB.
Developer Edition also starts at 0 MB, which catches people out when a class that worked in a colleague’s org does nothing in theirs. You can request a 10 MB trial from Setup, under Platform Cache, and it is approved on the spot. Trial capacity also expires, so a cache that “worked last month” and now misses every read is often a lapsed trial, not a bug.
You divide capacity into named partitions, each with an Org segment and a Session segment, allocated in whole megabytes, and the segments across all partitions cannot sum to more than your org’s total. A security-reviewed managed package gets its own 3 MB of provider free cache, separate from the subscriber’s capacity, which matters if you ship on the AppExchange.
The API is put, get, contains, remove
You reach a partition through Cache.Org.getPartition or Cache.Session.getPartition, then work it with four methods. An org without a set namespace uses local as the default, so a partition named config is addressed as local.config.
// Org segment of the "config" partition in the default (local) namespace
Cache.OrgPartition part = Cache.Org.getPartition('local.config');
part.put('usdRates', rateTable); // store a serializable value
if (part.contains('usdRates')) { // always check before you trust it
Map<String, Decimal> rates = (Map<String, Decimal>) part.get('usdRates');
}
part.remove('usdRates');
Keys are case-sensitive, alphanumeric, and capped at 50 characters; a single cached item can be at most 100 KB. A large object graph has to be split across keys or trimmed before it goes in. Time-to-live is yours to set: Org Cache entries default to 24 hours and max out at 48, and Session Cache tops out at 8 hours.
CacheBuilder is the pattern worth copying
The four-method API invites a bug. You check contains, and if it misses you compute the value and put it, and that check-then-load dance gets copy-pasted slightly wrong in a dozen classes. The Cache.CacheBuilder interface removes the dance.
You implement one method, doLoad, that builds the value. You ask for it with Cache.Org.get(MyBuilder.class, key). On a hit you get the cached value; on a miss the platform runs doLoad, caches the result, and returns it. One code path, no manual miss handling.
public class CurrencyRatesCache implements Cache.CacheBuilder {
public Object doLoad(String isoCode) {
// runs only on a cache miss
return RateService.fetchTable(isoCode);
}
}
// hit → cached table; miss → doLoad runs once, result is cached
Map<String, Decimal> rates =
(Map<String, Decimal>) Cache.Org.get(CurrencyRatesCache.class, 'USD');
One behaviour is worth knowing before you rely on it. If doLoad returns null, that null is handed back to the caller but not cached, so the next call runs doLoad again. Salesforce built it that way on purpose, so a temporary failure to build a value is not stored and served for 48 hours. The flip side is there is no negative caching: if you want to remember that something does not exist, store a sentinel value, not a null.
It is best-effort, so never the source of truth
Platform Cache is memory, and memory under pressure gets reclaimed. When a partition fills, the platform evicts the least recently used entries, and it can drop an item before the request even commits. An entry can be gone well before its time-to-live.
So every read has a miss path, and the miss path has to work. The CacheBuilder pattern gives you that for free; a hand-rolled get needs an explicit fallback to the real source. Code that assumes a hit is code that fails the first time maintenance clears the cache.
Two more facts shape how you use it. Values come back as immutable copies, so you cannot mutate a cached object in place; to change it you put a new value. And everything you cache has to be serializable, which covers sObjects, SOQL results, primitives, and collections of them, but will throw the moment you try to cache something that is not.
Why not just a static variable?
Because a static lives for one transaction. A static variable in Apex is cleared when the transaction ends and is never shared with another user or another request. The recursion guard in a trigger handler relies on exactly that: it has to forget everything between transactions, so a static is right there and Platform Cache would be wrong.
Platform Cache is the opposite tool. It deliberately survives across transactions and, for Org Cache, across users, which is the whole point when the thing you are avoiding is a repeated query or callout on the next request. Reach for a static for within-transaction memoisation, and for Platform Cache when the savings have to carry into the transactions that follow.
Where it pays, and where it doesn’t
It pays when data is expensive to produce, read far more than written, and tolerant of being briefly stale or absent. Reference tables, configuration, the output of a heavy aggregate, a parsed response from a slow callout. For a non-selective query you cannot fix cheaply, caching the result is sometimes the pragmatic move while you redesign the data model.
It does not pay for data that must be current to the row, for anything transactional, or for values small and static enough that a custom metadata type already serves them from the application cache with no code. And it is not a governor-limit escape hatch; it reduces queries and callouts on a hit, but a cold cache still does the full work, so your limits have to survive the miss.
The same logic applies when an Apex-backed Agentforce action feels slow. An action that reads the same configuration or recomputes the same lookup on every invocation is a fair candidate for a cached value, as long as the action still behaves correctly when the cache is empty. That is general performance practice applied to an agent, not an Agentforce-specific feature.
Test the miss path on purpose
Platform Cache has one test-context rule that surprises people: it cannot be used in a test annotated SeeAllData=true. Beyond that, the useful technique is to allocate 0 MB to a partition in the test setup, which forces a 100% miss rate and makes every get return null. That lets you prove the fallback works, which is the half of the code most likely to be wrong.
Because the cache is best-effort, the behaviour you most need to test is not the hit. It is what happens the moment the cache is empty, under real volume, the way a test that asserts would check any other failure path.
Where to start
Find the one method in your org that does expensive work for a result that rarely changes, and that enough requests hit to matter. A daily rate table, a parsed metadata response, an aggregate that scans a large object. Wrap it in a CacheBuilder, put it in an Org Cache partition sized to the data, and set a time-to-live that matches how often the underlying data moves.
Then unplug the cache in a test and make sure the feature still works, just slower. If it doesn’t, you haven’t built a cache, you’ve built a dependency on memory that the platform is allowed to take back at any time.
Understanding the basics
What is Salesforce Platform Cache used for?
It stores data in memory so Apex can reuse it across transactions instead of recomputing or re-querying it. Good candidates are data that is expensive to build, read far more often than it changes, and safe to lose, such as reference tables, configuration, or the parsed result of a slow callout.
What is the difference between Org Cache and Session Cache?
Org Cache is org-wide and readable by any user across sessions; use it for data everyone shares. Session Cache is scoped to a single user’s session and expires when its time-to-live runs out or the session ends, whichever comes first; use it for per-user data. Never put user-specific or sensitive data in Org Cache, because the whole org can read it.
How much Platform Cache do I get?
Enterprise Edition includes 10 MB by default, Unlimited and Performance 30 MB, and other editions 0 MB. Developer Edition starts at 0 MB but can request a 10 MB trial from Setup. You can buy more through your Salesforce account team, and a security-reviewed managed package gets a separate 3 MB of provider cache.
Is Platform Cache reliable enough to be a source of truth?
No. It is best-effort: entries can be evicted when a partition fills or during maintenance, sometimes before their time-to-live expires. Always write a working cache-miss path that falls back to the real source, and test that path by forcing a 100% miss rate.
Chasing slow transactions, repeated callouts, or an Apex-backed agent action that recomputes the same thing every time? Talk to us. Making an org fast and cheap to change again is the work we do.