# Getting Started with Caching



# Getting Started with Caching

If you've used `cachedWithin` on a query, you've already been caching. The cache BIFs give you the same idea — store something expensive once, reuse it — but for any data, with full control over what gets cached and when it expires.

## Why cache?

Every time your code runs an expensive operation — a slow query, an external API call, a complex calculation — the result is thrown away at the end of the request. The next request does the same work all over again.

Caching lets you keep that result around. One request does the expensive work, every subsequent request gets the answer from memory until you decide it's stale. Your database gets fewer queries, your external APIs get fewer calls, and your pages load faster.

## How Lucee's cache layer works

Lucee has a pluggable cache layer. You define a **cache connection** (think of it like a datasource, but for cached data), pick a backend (RAM, EHCache, Redis, DynamoDB), and your code talks to it through a small set of BIFs: [CachePut()](../reference/functions/cacheput.md), [CacheGet()](../reference/functions/cacheget.md), [CacheClear()](../reference/functions/cacheclear.md), and so on.

The backend is swappable. Your code doesn't change — just the config. Start with RAM for development, switch to Redis or DynamoDB for production when you need distributed caching across multiple servers.

Lucee also has the concept of **cache types**: object, query, function, template, resource, http, and others. When you set a cache connection as the "default object cache", the cache BIFs use it automatically without you specifying a name. When you set one as the "default query cache", `cachedWithin` on queries uses it. Same layer, different entry points.

Lucee ships with a built-in RAM cache and supports several extension-based providers (Redis, EHCache, DynamoDB, Memcached, MongoDB). See [Cache](../categories/cache.md) for the full list of providers with class names and Maven coordinates. Start with RamCache — it's zero-config and fast. When you outgrow a single server, swap in a distributed provider without changing your application code.

## Setting up a cache connection

Before you can cache anything, you need a cache connection. The easiest way is in `Application.cfc`:

```cfml
// Application.cfc
this.cache.connections["myCache"] = {
	class: 'lucee.runtime.cache.ram.RamCache',
	storage: false,
	custom: { timeToIdleSeconds: 3600, timeToLiveSeconds: 3600 },
	default: 'object'
};
```

That creates a RAM cache named "myCache" and sets it as the default object cache. The `custom` struct controls how long entries live — here, 1 hour for both idle timeout and absolute lifetime.

For the examples in this guide, we'll use `application action="update"` to define the cache inline — this makes the examples self-contained and runnable on [trycf.com](https://trycf.com):

```cfml
application action="update" caches="#{ myCache: {
	class: 'lucee.runtime.cache.ram.RamCache',
	custom: { timeToIdleSeconds: 3600, timeToLiveSeconds: 3600 },
	default: 'object'
}}#";

cachePut( "greeting", "G'day!" );
dump( cacheGet( "greeting" ) );
```

For full details on cache connection configuration, see [Adding Caches via Application.cfc](caches-defined-in-application-cfc.md).

## Storing and retrieving data

The core workflow is [CachePut()](../reference/functions/cacheput.md) and [CacheGet()](../reference/functions/cacheget.md). You store a value with a key, get it back later. Works with any CFML type — strings, structs, arrays, queries, components.

```cfml
application action="update" caches="#{ myCache: {
	class: 'lucee.runtime.cache.ram.RamCache',
	custom: { timeToIdleSeconds: 3600, timeToLiveSeconds: 3600 },
	default: 'object'
}}#";

cacheClear();

// cache a struct
cachePut( "user:42", {
	name: "Zac",
	email: "zac@example.com",
	plan: "pro"
} );

// retrieve it
user = cacheGet( "user:42" );
dump( user );
```

**Tip:** Use prefixed keys like `"user:42"` or `"config:site"` to namespace your cache entries. This makes wildcard filtering much easier later.

### Checking before getting

If you [CacheGet()](../reference/functions/cacheget.md) a key that doesn't exist, the return value is `null`. You can either check first with [CacheIdExists()](../reference/functions/cacheidexists.md), or handle the null:

```cfml
application action="update" caches="#{ myCache: {
	class: 'lucee.runtime.cache.ram.RamCache',
	custom: { timeToIdleSeconds: 3600, timeToLiveSeconds: 3600 },
	default: 'object'
}}#";

cacheClear();

// pattern 1: check first
if ( cacheIdExists( "user:99" ) ) {
	user = cacheGet( "user:99" );
}

// pattern 2: just get it, check for null
user = cacheGet( "user:99" );
if ( !isNull( user ) ) {
	dump( user );
} else {
	dump( "not cached" );
}
```

### Removing a single entry

[CacheDelete()](../reference/functions/cachedelete.md) removes one entry by its key:

```cfml
application action="update" caches="#{ myCache: {
	class: 'lucee.runtime.cache.ram.RamCache',
	custom: { timeToIdleSeconds: 3600, timeToLiveSeconds: 3600 },
	default: 'object'
}}#";

cacheClear();
cachePut( "user:42", "Zac" );
cachePut( "user:43", "Alice" );
dump( cacheCount() ); // 2

cacheDelete( "user:42" );
dump( cacheCount() ); // 1
```

By default, deleting a key that doesn't exist is silently ignored. Pass `throwOnError=true` if you want an exception instead.

## Exploring what's in the cache

When you need to see what's cached — for debugging, monitoring, or bulk operations:

```cfml
application action="update" caches="#{ myCache: {
	class: 'lucee.runtime.cache.ram.RamCache',
	custom: { timeToIdleSeconds: 3600, timeToLiveSeconds: 3600 },
	default: 'object'
}}#";

cacheClear();
cachePut( "user:42", "Zac" );
cachePut( "user:43", "Alice" );
cachePut( "config:theme", "dark" );

// how many entries?
dump( cacheCount() ); // 3

// what keys exist? (returns an array, keys are uppercased)
dump( cacheGetAllIds() );

// wildcard filter — get only user keys
dump( cacheGetAllIds( "user:*" ) );

// get everything as a struct (key -> value)
dump( cacheGetAll( "user:*" ) );
```

[CacheGetAllIds()](../reference/functions/cachegetallids.md) and [CacheGetAll()](../reference/functions/cachegetall.md) both support wildcard filters using the same pattern syntax as `cfdirectory` — `*` matches any sequence of characters.

## Clearing the cache

[CacheClear()](../reference/functions/cacheclear.md) with no arguments wipes everything. With a wildcard filter, it removes only matching entries:

```cfml
application action="update" caches="#{ myCache: {
	class: 'lucee.runtime.cache.ram.RamCache',
	custom: { timeToIdleSeconds: 3600, timeToLiveSeconds: 3600 },
	default: 'object'
}}#";

cacheClear();
cachePut( "user:42", "Zac" );
cachePut( "user:43", "Alice" );
cachePut( "config:theme", "dark" );

// clear only user entries
cacheClear( "user:*" );
dump( cacheCount() ); // 1 — config:theme remains

// clear everything
cacheClear();
dump( cacheCount() ); // 0
```

Use [CacheDelete()](../reference/functions/cachedelete.md) when you know the exact key. Use [CacheClear()](../reference/functions/cacheclear.md) with a filter when you want to wipe a category of entries. Use [CacheClear()](../reference/functions/cacheclear.md) with no arguments when you want a fresh start.

## Cache expiration

Cache entries can expire automatically. [CachePut()](../reference/functions/cacheput.md) takes two optional timespan arguments:

- **timeSpan** — the entry expires after this much time, regardless of access
- **idleTime** — the entry expires if it hasn't been accessed within this time

```cfml
application action="update" caches="#{ myCache: {
	class: 'lucee.runtime.cache.ram.RamCache',
	custom: { timeToIdleSeconds: 3600, timeToLiveSeconds: 3600 },
	default: 'object'
}}#";

cacheClear();

// expire after 5 minutes — good for API responses where you want freshness
cachePut( "api:weather", { temp: 22, wind: "NW" }, createTimeSpan( 0, 0, 5, 0 ) );

// expire after 5 min absolute, or 1 min idle — good for session-like data
cachePut( "session:abc", { user: "Zac" }, createTimeSpan( 0, 0, 5, 0 ), createTimeSpan( 0, 0, 1, 0 ) );

dump( cacheIdExists( "api:weather" ) ); // true (hasn't expired yet)
```

**When to use which:**

- **timeSpan only** — when data has a known freshness window (cache this API response for 5 minutes)
- **idleTime only** — when you want to keep popular entries warm but let cold ones expire
- **Both** — maximum lifetime capped by timeSpan, but idle entries drop out sooner

If you don't pass either, the cache connection's default TTL applies (the `timeToLiveSeconds` and `timeToIdleSeconds` from your config).

## Named caches

So far we've used the default object cache — the one set with `default: 'object'` in the config. But you can define multiple cache connections with different settings and use the `cacheName` parameter to target them:

```cfml
application action="update" caches="#{
	shortLived: {
		class: 'lucee.runtime.cache.ram.RamCache',
		custom: { timeToIdleSeconds: 60, timeToLiveSeconds: 300 },
		default: 'object'
	},
	longLived: {
		class: 'lucee.runtime.cache.ram.RamCache',
		custom: { timeToIdleSeconds: 86400, timeToLiveSeconds: 86400 }
	}
}#";

// API responses in the short-lived cache (5 min TTL)
cachePut( id: "api:weather", value: { temp: 22 }, cacheName: "shortLived" );

// config data in the long-lived cache (24 hour TTL)
cachePut( id: "config:features", value: { darkMode: true }, cacheName: "longLived" );

dump( cacheGet( id: "api:weather", cacheName: "shortLived" ) );
dump( cacheGet( id: "config:features", cacheName: "longLived" ) );
```

Every cache BIF — [CacheGet()](../reference/functions/cacheget.md), [CachePut()](../reference/functions/cacheput.md), [CacheClear()](../reference/functions/cacheclear.md), [CacheDelete()](../reference/functions/cachedelete.md), [CacheCount()](../reference/functions/cachecount.md), [CacheGetAllIds()](../reference/functions/cachegetallids.md), [CacheGetAll()](../reference/functions/cachegetall.md), [CacheIdExists()](../reference/functions/cacheidexists.md) — accepts an optional `cacheName` parameter.

## A practical example

Here's a common pattern — cache-aside (also called lazy loading). Check the cache first, only do the expensive work on a miss:

```cfml
function getUserProfile( required string userId ) {
	var cacheKey = "userProfile:" & arguments.userId;

	// try the cache first
	var cached = cacheGet( cacheKey );
	if ( !isNull( cached ) ) return cached;

	// cache miss — do the expensive work
	var profile = queryExecute(
		"SELECT name, email, plan FROM users WHERE id = :id",
		{ id: arguments.userId }
	);

	// store it for 10 minutes
	cachePut( cacheKey, profile, createTimeSpan( 0, 0, 10, 0 ) );

	return profile;
}
```

When the user updates their profile, flush just that one entry:

```cfml
cacheDelete( "userProfile:" & userId );
```

Next request gets a fresh copy from the database.

# Categories

[Cache](../categories/cache.md)

# See Also

[Using cachedWithin](cached-within-request.md), [Adding Caches via Application.cfc](caches-defined-in-application-cfc.md), [DynamoDB Cache Extension](dynamodb-cache-extension.md), [CacheClear()](../reference/functions/cacheclear.md), [CacheCount()](../reference/functions/cachecount.md), [CacheDelete()](../reference/functions/cachedelete.md), [CacheGet()](../reference/functions/cacheget.md), [CacheGetAll()](../reference/functions/cachegetall.md), [CacheGetAllIds()](../reference/functions/cachegetallids.md), [CacheGetMetadata()](../reference/functions/cachegetmetadata.md), [CacheIdExists()](../reference/functions/cacheidexists.md), [CachePut()](../reference/functions/cacheput.md), [List existing Cache Connections](list-existing-cache-conn.md), [Selective Cache Invalidation](cache-invalidation-selective.md)