Skip to main content

Salesforce Platform Cache Explained: Org vs Session Cache & Best Practices

๐Ÿ’ฌ In plain words: Platform Cache is Salesforce's short-term memory layer. You pay the performance cost once to run a heavy query or API call out, and then you store the result. Thousands of subsequent requests can read that stored result in microseconds. Org cache is shared by everyone; session cache is specific to a single user. Golden rule: Cache is a temporary hint, not a permanent database. Always write your code to handle a cache miss.

Key Points at a Glance

  • Two Scopes: Org Cache (shared across all users) and Session Cache (tied to one specific user's session).
  • Temporary Storage: Items in the cache can be evicted at any time to free up space. Your code must always be prepared to rebuild the data.
  • Best Pattern: Use Cache.CacheBuilder to automatically handle cache misses and rebuild data transparently.
  • Limits: Cache space is divided into Partitions. You manage capacity based on your org's allocated limits.
Salesforce Platform Cache Architecture

Understanding Platform Cache

Platform Cache is managed, in-memory storage designed to make your Salesforce applications run faster. Instead of repeatedly querying the database for data that rarely changes, you stash it in memory. You organize this memory into Partitions to control how much space different applications get.

You interact with the cache via Apex using the Cache.OrgPartition and Cache.SessionPartition classes. However, modern developers prefer the declarative Cache.CacheBuilder interface because it naturally forces you to handle the "load-on-miss" pattern gracefully.

๐Ÿงญ 360 Card — Platform Cache
  • Rule: Pay once, read many. Cache data that is stable, widely shared, and computationally expensive to build.
  • Gain: Microsecond read times instead of burning database queries or API callouts. Saves your transaction limits.
  • Reach for: Use Session Cache when the data is unique per user. Use Org Cache when the data is the same for everyone.
  • Price: It is a speed layer, not a hard drive. Entries can disappear at any moment, meaning every read must have a fallback plan.
  • Limits: Cache partitions have strict memory budgets. Never cache sensitive secrets, and never cache record data that requires strict sharing rule evaluations.
  • The Alternative: Querying the database every time. It's always accurate but burns precious SOQL limits on data that might only change once a week.
  • Best Practice: Use CacheBuilder. It standardizes checking the cache and automatically rebuilding it if it's empty.
  • At Volume: If you set a long Time-To-Live (TTL) without a way to invalidate the cache, your users will be stuck staring at stale data.

Real-Life Example: Paying for the Same Answer Every Click

The Scenario: Every screen in your Lightning console displays today's fuel exchange rates. These rates only change once every 24 hours.

The Old/Bad Way: Every time a user loads a page, Lightning Web Components (LWC) call Apex, which runs a SOQL query to fetch the rates. If 40 users load dozens of pages, you are hammering the database for the exact same answer thousands of times an hour. Pages load slower, and SOQL limits are wasted.

The New/Good Way:

  1. Create an Org Cache partition.
  2. Access the data using a CacheBuilder class.
  3. When the first user loads the page, the cache misses. The code queries the database, stores the rates in the cache, and sets a TTL of 4 hours.
  4. For the next 4 hours, thousands of page loads read the rates directly from memory in microseconds.

The Payoff: One SOQL query now feeds thousands of reads. You paid the database cost once, and read it for free everywhere else.

How CacheBuilder Works

Here is what the CacheBuilder pattern looks like in practice. Notice how you only write the logic to build the data. Salesforce handles checking the cache and storing the result.

public class ExchangeRateCache implements Cache.CacheBuilder {
    // This method is ONLY called if the cache is empty or expired
    public Object doLoad(String key) {
        return [SELECT CurrencyIsoCode, ConversionRate 
                FROM CurrencyType 
                WHERE IsActive = true];
    }
}

// To use it anywhere in your code:
// If it's in cache, it returns instantly. If not, doLoad() runs automatically.
List<CurrencyType> rates = (List<CurrencyType>) Cache.Org.get(ExchangeRateCache.class, 'dailyRates');
๐Ÿง  Core Takeaway: Pay for the query or callout once, read it free many times. Never assume data is still sitting in the cache.
⚠ INTERVIEW TRAP: Platform Cache is NOT guaranteed storage. Never treat it like a custom object. Any entry can be evicted by the system at any time to free up memory. If your code breaks because a cache key returns null, you have built it wrong. Every single read must be able to survive a cache miss.

Core Q&A and Common Scenarios

Q: What belongs in Platform Cache, what must never go in it, and why is CacheBuilder the preferred access pattern?
๐ŸŽฏ Say this first: "Cache what is stable, shared, and expensive to build. Never cache secrets, and never cache shared records."

A: You want to cache expensive, frequently-read, slowly-changing data. Good examples include org-wide configuration objects, exchange rates, category trees, and complex per-session user context.

You must never cache:

  • Data you cannot afford to lose (since the cache is evictable).
  • Rapidly-changing transactional data (to avoid stale data issues).
  • Unencrypted secrets or passwords (use Protected Custom Metadata or Named Credentials instead).

CacheBuilder wins because it makes handling a cache miss structural. By implementing the doLoad(key) method, every read either hits the cache or transparently rebuilds it. This eliminates the classic bug where a developer forgets to write a fallback query for a cache miss.

Q: Should I use Org cache or Session cache for a complex per-user permissions matrix that takes 6 queries to build?

A: Use Session Cache. Because the data is highly specific to the logged-in user, putting it in Org Cache would require complex, user-ID-suffixed keys and would burn through your shared partition limit on data only one person cares about. Session cache naturally scopes to the user and dies when they log out, which automatically limits how stale the data can get.

Follow-up Trap: Session cache is not available in asynchronous Apex (like Batchable or Queueable) because there is no active browser session. If you need this matrix in a Queueable, you must either recompute it, or temporarily use Org Cache with user-suffixed keys and manage the invalidation yourself.

Q: After deploying caching, users are complaining they see old, stale reference data for hours. How do you fix this?

A: The cache was likely deployed with a long Time-To-Live (TTL) but no invalidation strategy. There are three ways to fix this:

  • Event-Driven Invalidation: Whenever a record changes, use an Apex Trigger to explicitly remove or refresh the cached keys using Cache.Org.remove(). You can also publish a Platform Event to handle this asynchronously.
  • Version-Stamped Keys: Include a version number in your cache key. When data changes, bump the version number. Stale entries are simply never requested again and age out naturally.
  • Honest TTLs: Match your TTL to the actual business tolerance for stale data. If the business can't handle data being older than 5 minutes, set a 5-minute TTL. The golden rule is: A caching design that only adds data but never invalidates it is an unfinished design.
Q: How do you handle Platform Cache in Apex Unit Tests?

A: By default, cache hits always return null in test classes to ensure test isolation. If you are specifically trying to test your caching logic, you must manually populate the cache within the test method using Cache.Org.put(), or better yet, structure your code so that test methods focus on testing the doLoad() miss-handling logic to ensure data rebuilds correctly.