What You'll Learn
The difference between Org Cache, Session Cache, and Partition Cache, the API for reading and writing cache values from Apex, appropriate caching patterns, invalidation strategies, and the common mistakes that lead to stale data bugs.
Why Platform Cache Exists
Salesforce governor limits mean that every SOQL query, every callout, and every computation counts against a per-transaction budget. Data that changes rarely — Custom Metadata configurations, product rate tables, org-wide settings, lookup picklist values — is queried repeatedly on every page load or trigger execution. Platform Cache lets you store this data in memory and skip the SOQL entirely on subsequent accesses.
The performance improvement is significant: a cached value returns in microseconds versus a SOQL query that takes 5-50 milliseconds. For high-volume orgs with hundreds of concurrent users, this difference matters for both user experience and Salesforce platform stability.
Three Cache Tiers: Org, Session, and Partition
Org Cache stores data shared across all users in the org. A cached value written by User A is readable by User B. This makes it suitable for global configuration data, shared lookup tables, and org-wide computed values. Org Cache has a maximum TTL of 48 hours and a default capacity of 10 MB (expandable via Platform Cache licence add-on).
Session Cache stores data scoped to a single user's Lightning session. Data written during one page load is available on the next page load for the same user, but other users cannot read it. This is appropriate for user-specific preferences, mid-flow state, or data that varies per user but is expensive to recompute.
Partition Cache is the namespace concept within each tier. A partition is a named allocation within the Org or Session cache, created in Setup. Partitions allow packages and application components to manage their own cache space without colliding with other components' keys. Default partition names resolve to local.
// Writing to Org Cache
Cache.OrgPartition orgPart = Cache.Org.getPartition('local.myConfig');
orgPart.put('productRateTable', rateTableData, 3600); // TTL: 1 hour
// Reading from Org Cache
Map<String,Decimal> rates =
(Map<String,Decimal>) orgPart.get('productRateTable');
if (rates == null) {
// Cache miss — reload from SOQL
rates = loadRatesFromSoql();
orgPart.put('productRateTable', rates, 3600);
}
// Writing to Session Cache
Cache.SessionPartition sessPart =
Cache.Session.getPartition('local.userPrefs');
sessPart.put('dashboardLayout', layoutData, 1800);
// Checking cache capacity
Cache.OrgPartition p = Cache.Org.getPartition('local.myConfig');
Long used = p.getNumKeys();
Long capacity = p.getCapacity();Cache.CacheBuilder interface OR be serialisable (primitive types, custom objects with no transient fields, standard Apex types). Classes with static variables, non-serialisable system types (HttpRequest, Database.QueryLocator), or inner classes with references to outer-class state cannot be cached.
The CacheBuilder Pattern
The Cache.CacheBuilder interface provides a cleaner cache-or-load pattern that avoids the repetitive null-check boilerplate. Implement the interface, and the Cache.Org.get() method handles the miss automatically by calling your builder.
// CacheBuilder pattern
public class ProductRateCacheBuilder implements Cache.CacheBuilder {
public Object doLoad(String requestedKey) {
// requestedKey is the key passed to Cache.Org.get()
return [SELECT Id, Rate__c FROM ProductRate__c
WHERE ProductCode__c = :requestedKey LIMIT 1];
}
}
// Usage — automatically loads on cache miss
ProductRate__c rate =
(ProductRate__c) Cache.Org.get(ProductRateCacheBuilder.class, 'PROD-001');
// No null check needed — CacheBuilder guarantees a value or throwsInvalidation Strategies
Cache invalidation is the classic "hard problem" — data in cache goes stale when the underlying SOQL result changes. Three patterns handle this:
- TTL expiry: Set the TTL to match the maximum acceptable staleness. For a rate table that is updated weekly, a 24-hour TTL is appropriate. Simple but imprecise — you may serve stale data for up to TTL duration.
- Trigger-based invalidation: Add a trigger on the source object that calls
orgPart.remove('productRateTable')whenever the source data changes. The next cache read triggers a reload. Precise but adds trigger complexity. - Version key pattern: Store a version number alongside the data. Increment the version when data changes. Cache keys incorporate the version number — stale keys simply expire unused. Useful when multiple callers cache derived views of the same source data.
Cache in Managed Packages and Sandboxes
Platform Cache is cleared on sandbox refresh. Any warm-cache assumptions built into application code will fail immediately after a sandbox refresh. Design cache-miss paths to handle cold starts gracefully — the application must function correctly (slowly) when the cache is empty.
In managed packages, use namespaced partition names to avoid collision with customer org cache content. A partition named mypackage.config is isolated from the customer's local.config partition. Declare required partition sizes in the package's Feature Parameters so customers know what to allocate.
Key Takeaways
- Org Cache is shared across all users — suitable for global configuration data but never for user-specific or security-sensitive data.
- Session Cache is user-scoped — suitable for user preferences and per-user computed state.
- Partitions are named namespaces within each cache tier — use them to prevent key collisions between packages and application components.
- The CacheBuilder interface eliminates null-check boilerplate by handling cache miss and reload automatically.
- Org Cache is cleared on sandbox refresh — always code for cold-start (cache-empty) operation.
- Monitor cache hit rates in production — below 80% means the caching strategy needs revision.
Check Your Understanding
1. You need to cache a product rate table that is identical for all users and updated once per day. Which cache tier is appropriate?
2. What happens to Platform Cache content when a sandbox is refreshed?
3. Which approach provides the most precise cache invalidation when the source data changes?
Discussion & Feedback