| name | cachebox-standalone |
|---|---|
| description | Use this skill when working with CacheBox as a standalone caching framework (outside ColdBox) -- installing, creating and bootstrapping the CacheFactory, configuring the DSL, choosing object stores and eviction policies, selecting cache providers (CacheBoxProvider, CF, Lucee), implementing cache-aside/stampede-protection patterns, registering standalone listeners, named caches, disk/JDBC stores, reaping, shutdown, or monitoring cache performance. |
Use this skill when using CacheBox outside of a ColdBox application — as a pure standalone caching library. For ColdBox-integrated caching (WireBox injection, event/view caching, @cacheable), use the coldbox-cache-integration skill instead.
Examples use CFML script unless noted. For BoxLang replace component with class and use cachebox.system or coldbox.system depending on build.
CacheBox is an enterprise-grade, standalone caching aggregator for ColdFusion/BoxLang that:
- Aggregates multiple named cache providers under one API
- Ships built-in storage backends: RAM (ConcurrentStore), memory-sensitive (ConcurrentSoftReferenceStore), Disk, and JDBC
- Provides pluggable eviction policies: LRU, LFU, FIFO, LIFO, custom
- Broadcasts a rich event model through cache listeners
- Runs embedded in ColdBox or as a pure standalone library
Namespace: Standalone =
cachebox.system.*; ColdBox-embedded =coldbox.system.*
box install cacheboxMap the installed folder in Application.cfc:
this.mappings[ "/cachebox" ] = expandPath( "/path/to/cachebox" );
If using CacheBox inside ColdBox, skip installation — it is already included.
| Mode | Who creates CacheBox | Note |
|---|---|---|
| Standalone | You — in onApplicationStart |
Persist in application scope or WireBox |
| ColdBox embedded | ColdBox framework | Use coldbox-cache-integration skill |
application.cacheBox = new cachebox.system.cache.CacheFactory();
var cache = application.cacheBox.getDefaultCache();
cache.set( "myKey", { name : "Luis" }, 60, 20 ); // 60 min TTL, 20 min idle
var obj = cache.get( "myKey" );
Create config/CacheBox.cfc:
component {
function configure() {
cacheBox = {
// Used only in standalone mode for console/file logging
logBoxConfig : "cachebox.system.cache.config.LogBox",
// Auto-registers the factory in application scope as application.cacheBox
scopeRegistration : {
enabled : true,
scope : "application",
key : "cacheBox"
},
// MANDATORY. Name "default" is reserved; provider cannot be changed.
// All timeouts are in MINUTES.
defaultCache : {
objectDefaultTimeout : 60,
objectDefaultLastAccessTimeout : 30,
useLastAccessTimeouts : true,
reapFrequency : 2,
freeMemoryPercentageThreshold : 0, // 0 = no JVM memory check
evictionPolicy : "LRU",
evictCount : 1,
maxObjects : 300,
objectStore : "ConcurrentSoftReferenceStore",
coldboxEnabled : false, // MUST be false in standalone mode
resetTimeoutOnAccess : false
},
// Named caches — add as many domains as needed
caches : {
// Fast, guaranteed in-memory cache for database queries
queryCache : {
provider : "cachebox.system.cache.providers.CacheBoxProvider",
properties : {
objectDefaultTimeout : 30,
objectDefaultLastAccessTimeout : 15,
maxObjects : 500,
objectStore : "ConcurrentStore" // guaranteed — no JVM eviction
}
},
// Memory-sensitive view/fragment cache
viewCache : {
provider : "cachebox.system.cache.providers.CacheBoxProvider",
properties : {
objectDefaultTimeout : 120,
objectDefaultLastAccessTimeout : 30,
maxObjects : 200,
objectStore : "ConcurrentSoftReferenceStore"
}
},
// Disk-backed cache (survives app restarts; serialises complex objects)
diskCache : {
provider : "cachebox.system.cache.providers.CacheBoxProvider",
properties : {
objectDefaultTimeout : 1440, // 24 hours
maxObjects : 1000,
objectStore : "DiskStore",
directoryPath : "/var/cache/myapp",
autoExpandPath : false
}
},
// JDBC-backed cache (small clusters; stores in a DB table)
dbCache : {
provider : "cachebox.system.cache.providers.CacheBoxProvider",
properties : {
objectDefaultTimeout : 60,
maxObjects : 1000,
objectStore : "JDBCStore",
dsn : "myDatasource",
table : "cachebox_objects",
tableAutoCreate : true
}
}
},
// Standalone listeners — executed in declaration order
listeners : [
{ class : "myapp.listeners.CacheLogger", name : "CacheLogger", properties : {} }
]
};
}
}
Bootstrap in Application.cfc:
component {
this.name = "MyApp";
// Required standalone mapping
this.mappings[ "/cachebox" ] = expandPath( "/lib/cachebox" );
this.mappings[ "/myapp" ] = expandPath( "/" );
function onApplicationStart() {
var config = new cachebox.system.cache.config.CacheBoxConfig(
CFCConfigPath : "config.CacheBox"
);
// scopeRegistration in config auto-sets application.cacheBox
new cachebox.system.cache.CacheFactory( config );
}
function onRequestStart() {
// REQUIRED in standalone mode — triggers expiry sweep per reapFrequency
application.cacheBox.reapAll();
}
function onApplicationEnd( applicationScope ) {
if ( structKeyExists( applicationScope, "cacheBox" ) ) {
applicationScope.cacheBox.shutdown();
}
}
}
var config = new cachebox.system.cache.config.CacheBoxConfig()
.scopeRegistration( true, "application", "cacheBox" )
.defaultCache(
maxObjects : 200,
objectStore : "ConcurrentStore",
evictionPolicy : "LRU"
)
.cache(
"queryCache",
"cachebox.system.cache.providers.CacheBoxProvider",
{ objectDefaultTimeout : 30, maxObjects : 500, objectStore : "ConcurrentStore" }
)
.listener( "myapp.listeners.CacheLogger", {}, "CacheLogger" );
new cachebox.system.cache.CacheFactory( config );
new cachebox.system.cache.CacheFactory( {
scopeRegistration : { enabled : true, scope : "application", key : "cacheBox" },
defaultCache : {
objectDefaultTimeout : 60,
maxObjects : 300,
objectStore : "ConcurrentSoftReferenceStore",
coldboxEnabled : false
},
caches : {},
listeners : []
} );
// From application scope (set by scopeRegistration)
var cacheBox = application.cacheBox;
// Always-available default cache
var cache = cacheBox.getDefaultCache();
// Named cache
var queryCache = cacheBox.getCache( "queryCache" );
// All registered cache names
var names = cacheBox.getCacheNames(); // array
// Add a default-type cache at runtime
var extra = cacheBox.addDefaultCache( "SessionCache" );
All providers share the ICacheProvider API. Timeouts are in minutes.
// ── WRITE ────────────────────────────────────────────────────────────────────
// set( key, value [, timeout [, lastAccessTimeout]] )
cache.set( "product_1", product, 60, 30 ); // 60 min TTL, 30 min idle
cache.set( "product_1", product ); // use configured defaults
cache.set( "config_data", data, 0, 0 ); // eternal (lives until explicit clear)
// ── READ ─────────────────────────────────────────────────────────────────────
var val = cache.get( "product_1" ); // null if missing or expired; resets idle timer
var val = cache.getQuiet( "product_1" ); // null if missing or expired; does NOT reset idle timer
// ── CHECK ────────────────────────────────────────────────────────────────────
var exists = cache.lookup( "product_1" ); // true/false, respects expiry, resets idle
var exists = cache.lookupQuiet( "product_1" ); // true/false, no idle reset
// ── GET-OR-SET ────────────────────────────────────────────────────────────────
var product = cache.getOrSet(
objectKey : "product_1",
produce : function() { return loadProductFromDB( 1 ) },
timeout : 60,
lastAccess : 30
);
// ── METADATA ─────────────────────────────────────────────────────────────────
var meta = cache.getCachedObjectMetadata( "product_1" );
// meta.timeout, meta.lastAccessTimeout, meta.hits, meta.lastAccessed, meta.isExpired
// ── DELETE ───────────────────────────────────────────────────────────────────
cache.clear( "product_1" ); // single key
cache.clearByKeySnippet( "product_", false ); // all keys containing "product_"
cache.clearByKeySnippet( "^product_\d+$", true ); // regex match
cache.clearAll(); // all keys in this provider
cache.expireObject( "product_1" ); // lazy expire (removed on next reap)
cache.expireAll(); // lazy expire entire cache
// ── STATS ────────────────────────────────────────────────────────────────────
var keys = cache.getKeys(); // array of all live keys
var size = cache.getSize(); // count of live objects
var stats = cache.getStats(); // struct: hits, misses, evictions, performance
function getProduct( required numeric id ) {
var key = "product_#id#"
var product = cache.get( key )
if ( !isNull( product ) ) {
return product
}
product = productGateway.findById( id )
cache.set( key, product, 60, 30 )
return product
}
function getProduct( required numeric id ) {
return cache.getOrSet(
objectKey : "product_#id#",
produce : function() { return productGateway.findById( id ) },
timeout : 60,
lastAccess : 30
)
}
Use under high concurrency when the loader is expensive:
function getWithStampedeProtection( required string key, required any loader, numeric timeout = 60 ) {
// Fast path — no lock
var value = cache.get( key )
if ( !isNull( value ) ) { return value }
// Slow path — only one thread loads; others wait, then read from cache
lock name="cache_load_#key#" timeout="15" throwontimeout="false" type="exclusive" {
value = cache.get( key ) // re-check inside lock
if ( isNull( value ) ) {
value = loader()
cache.set( key, value, timeout )
}
}
return value
}
function save( required struct entity ) {
var saved = repository.save( entity )
cache.set( "entity_#saved.id#", saved, 60, 30 )
return saved
}
function delete( required numeric id ) {
repository.delete( id )
cache.clear( "entity_#id#" )
cache.clearByKeySnippet( "entity_list_" ) // invalidate related list caches
}
// myapp/listeners/CacheWarmer.cfc (standalone listener)
component {
function configure( required any cacheBox, required struct properties ) {
variables.cacheBox = arguments.cacheBox;
}
function afterCacheFactoryConfiguration( interceptData ) {
var cache = variables.cacheBox.getCache( "queryCache" )
var products = new myapp.gateways.ProductGateway().findAllActive()
for ( var p in products ) {
cache.set( "product_#p.id#", p, 240 )
}
}
}
| Provider | Use when |
|---|---|
cachebox.system.cache.providers.CacheBoxProvider |
Standalone apps — native CacheBox engine |
cachebox.system.cache.providers.CacheBoxColdBoxProvider |
ColdBox apps (default cache only, coldboxEnabled:true) |
cachebox.system.cache.providers.CFProvider |
Delegate to Adobe CF built-in cache (standalone) |
cachebox.system.cache.providers.CFColdBoxProvider |
Delegate to Adobe CF built-in cache (ColdBox) |
cachebox.system.cache.providers.LuceeProvider |
Delegate to Lucee built-in cache (standalone) |
cachebox.system.cache.providers.LuceeColdBoxProvider |
Delegate to Lucee built-in cache (ColdBox) |
cachebox.system.cache.providers.MockProvider |
Testing / no-op |
Always use CacheBoxProvider in standalone apps unless delegating to the CFML engine cache.
Object stores define where CacheBoxProvider physically stores objects:
| Store | Retention | Best For |
|---|---|---|
ConcurrentStore |
Guaranteed until evicted/expired/cleared | General data, queries — objects won't disappear under memory pressure |
ConcurrentSoftReferenceStore |
JVM may collect under heap pressure | View/fragment caching; memory-sensitive use cases |
DiskStore |
Survives restarts; disk I/O cost | Large payloads, cross-restart persistence |
JDBCStore |
Database-backed; survives restarts | Small clusters needing shared/centralised cache |
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
directoryPath |
string | ✅ | — | Path to store files |
autoExpandPath |
boolean | ❌ | true |
Expand relative paths with expandPath() |
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
dsn |
string | ✅ | — | Datasource name |
table |
string | ✅ | — | Table name |
dsnUsername |
string | ❌ | — | DSN username |
dsnPassword |
string | ❌ | — | DSN password |
tableAutoCreate |
boolean | ❌ | true |
Auto-create table if missing |
ACF + JDBCStore inside
<cftransaction>: All DB tags must use identicaldsn/username/password. Omit them and rely onthis.datasourceinApplication.cfcto avoid the "Usernames and Passwords must be the same" error.
| Policy | Algorithm | Use When |
|---|---|---|
| LRU (default) | Evicts least recently accessed | General-purpose |
| LFU | Evicts least frequently accessed | Hot-spot key sets |
| FIFO | Evicts oldest inserted | Queue semantics |
| LIFO | Evicts newest inserted | Stack semantics |
| Custom path | Your IEvictionPolicy impl |
Special requirements |
Eternal objects (
timeout = 0) are never evicted by policy — they live until explicitclear()or factoryshutdown().
CacheBox does not self-reap in standalone mode. Trigger on every request:
function onRequestStart() {
// Internally throttled to reapFrequency — safe to call every request
application.cacheBox.reapAll();
}
var cacheBox = application.cacheBox;
cacheBox.addCache( myCustomCacheInstance ); // register a pre-built provider
cacheBox.addDefaultCache( "TempCache" ); // add default-type cache by name
cacheBox.removeCache( "TempCache" ); // unregister and shut down a cache
cacheBox.replaceCache( original, decorated ); // swap with a decorated instance
cacheBox.expireAll(); // lazy-expire everything in all caches
cacheBox.clearAll(); // immediately clear all caches
cacheBox.reapAll(); // sweep all caches for expired objects
cacheBox.shutdown(); // graceful shutdown (always call on app stop)
cacheBox.getCacheNames(); // array of registered cache names
cacheBox.isCacheRegistered( "queryCache" ); // boolean
Listeners are CFCs with methods named after the events they handle. Order of execution = declaration order.
// myapp/listeners/CacheLogger.cfc
component {
// standalone configure signature — receives the factory + properties
function configure( required any cacheBox, required struct properties ) {
variables.cacheBox = arguments.cacheBox;
variables.log = arguments.cacheBox.getLogBox().getLogger( this );
}
// ── CacheFactory events ────────────────────────────────────────────────
function afterCacheFactoryConfiguration( interceptData ) {
variables.log.info( "CacheBox ready. Caches: #variables.cacheBox.getCacheNames().toList()#" );
}
function beforeCacheFactoryShutdown( interceptData ) {
variables.log.warn( "CacheBox shutting down" );
}
// ── CacheBoxProvider events ────────────────────────────────────────────
function afterCacheElementInsert( interceptData ) {
variables.log.debug(
"INSERT [#interceptData.cache.getName()#] #interceptData.cacheObjectKey# ttl=#interceptData.cacheObjectTimeout#"
);
}
function afterCacheElementRemoved( interceptData ) {
variables.log.debug(
"REMOVE [#interceptData.cache.getName()#] #interceptData.cacheObjectKey#"
);
}
function afterCacheElementExpired( interceptData ) {
variables.log.debug(
"EXPIRE [#interceptData.cache.getName()#] #interceptData.cacheObjectKey#"
);
}
function afterCacheClearAll( interceptData ) {
variables.log.info( "CLEAR ALL [#interceptData.cache.getName()#]" );
}
}
| Event | interceptData keys |
When fired |
|---|---|---|
afterCacheFactoryConfiguration |
cacheFactory |
After factory is fully started |
beforeCacheFactoryShutdown |
cacheFactory |
Before graceful shutdown |
afterCacheFactoryShutdown |
cacheFactory |
After shutdown completes |
afterCacheRegistration |
cache |
After addCache() |
beforeCacheRemoval |
cache |
Before removeCache() |
afterCacheRemoval |
cache (name string) |
After cache removed |
beforeCacheReplacement |
oldCache, newCache |
Before replaceCache() |
beforeCacheShutdown |
cache |
Before individual cache shutdown |
afterCacheShutdown |
cache |
After individual cache shutdown |
| Event | interceptData keys |
When fired |
|---|---|---|
afterCacheElementInsert |
cache, cacheObject, cacheObjectKey, cacheObjectTimeout, cacheObjectLastAccessTimeout |
After set() inserts a new key |
afterCacheElementUpdated |
cache, cacheNewObject, cacheOldObject |
After set() replaces existing key |
afterCacheElementRemoved |
cache, cacheObjectKey |
After clear() |
afterCacheElementExpired |
cache, cacheObjectKey |
Reaper found expired key |
afterCacheClearAll |
cache |
After clearAll() |
var cache = application.cacheBox.getDefaultCache();
// Performance stats struct
var stats = cache.getStats();
// stats.hits, stats.misses, stats.objectsAdded, stats.evictions, etc.
// Inspect a key
var meta = cache.getCachedObjectMetadata( "product_1" );
// meta.timeout, meta.lastAccessTimeout, meta.hits, meta.lastAccessed, meta.isExpired
// Inventory
var keys = cache.getKeys();
var size = cache.getSize();
Replace a real provider with a no-op mock during tests:
function beforeAll() {
var cacheBox = application.cacheBox;
var mockCache = new cachebox.system.cache.providers.MockProvider();
mockCache.setName( "default" );
mockCache.setConfiguration( {} );
mockCache.init( cacheBox );
cacheBox.replaceCache( cacheBox.getDefaultCache(), mockCache );
}
- Always call
shutdown()inonApplicationEnd— ensures disk/JDBC stores flush cleanly. - Always call
reapAll()inonRequestStart— mandatory in standalone mode. - Persist in
applicationscope viascopeRegistrationor explicit assignment; never re-instantiate per request. - Use
ConcurrentStorewhen objects must survive JVM memory pressure. UseConcurrentSoftReferenceStorefor view/template caches where graceful eviction under memory pressure is acceptable. DiskStore/JDBCStorerequire Java-serialisable objects — structs, arrays, and simple scalars are safe; CFC instances must be serialisable.- Namespace all cache keys — prefix with domain and identifier (
"product_#id#","user_#id#_prefs") to avoid collisions across teams/modules. - Separate caches by data domain — different TTLs, sizes, and eviction policies per domain (queries vs. views vs. config).
- Use
getOrSet()to eliminate null-check boilerplate and reduce stampede risk. - Use
clearByKeySnippet()overclearAll()when invalidating a group — avoid mass cache stampedes. - Eternal objects (timeout=0) are never evicted by policy — limit to true app-lifecycle singletons (e.g., reference lookup tables).
- Monitor
getStats()— low hit rates indicate key naming or TTL problems, not a need to increasemaxObjects. - Order listeners intentionally — they run in declaration order; put cache-warming listeners before auditing ones.