๐Ÿ”ท Core

coldbox-cache-integration

Use this skill when implementing caching inside a ColdBox application -- configuring CacheBox via ColdBox.cfc or config/CacheBox.cfc, injecting caches with WireBox (cachebox:name), using getCache() in handlers, event/view output caching with setEventCacheableEntry(), view fragment caching with renderView(cache=true), query caching, cache listeners as ColdBox interceptors, Redis/distributed provider setup, or choosing between default and template caches.

$ npx skills add coldbox/skills/coldbox/cache-integration
$ coldbox ai skills install coldbox/skills/coldbox/cache-integration
๐Ÿ”— https://skills.boxlang.io/skills/raw/coldbox/skills/coldbox~cache-integration

CacheBox โ€” ColdBox Integration

When to Use This Skill

Use this skill when adding caching to a ColdBox application. For standalone CacheBox usage (no ColdBox framework), see the cachebox-standalone skill.

Language Mode Reference

Examples use BoxLang (.bx) syntax by default. For CFML replace class with component and use property name="x" inject="y"; annotations.

ConceptBoxLangCFML
Classclass {component {
WireBox inject annotation@inject( "dsl" ) above propertyproperty name="x" inject="dsl";
View files.bxm.cfm

1. How ColdBox Wires CacheBox

ColdBox creates and manages a CacheBox factory automatically. No Application.cfc bootstrap code is needed. The factory is accessible via:

// From any handler, interceptor, or service
var cacheBox  = getCacheBox();          // the CacheBox factory
var defCache  = getCache();             // default cache provider
var tplCache  = getCache( "template" ); // named cache provider

2. Configuration

ColdBox looks for CacheBox config in this order:

  1. cachebox DSL struct inside config/ColdBox.cfc โ†’ configure()
  2. config/CacheBox.cfc (portable CFC by convention โ€” recommended)
  3. Built-in defaults at coldbox/system/web/config/CacheBox.cfc

2a. Default built-in caches (no config required)

ColdBox ships with two built-in caches:

Cache nameObject storePurpose
defaultConcurrentStore (guaranteed)General data, objects, query results
templateConcurrentSoftReferenceStore (memory-sensitive)Event output caching, view fragment caching

Use the defaults until you need different TTLs, size limits, or distributed providers.

// config/CacheBox.cfc
component {

    function configure() {
        cacheBox = {
            // All timeouts in MINUTES

            // MANDATORY. "default" is reserved. coldboxEnabled MUST be true in ColdBox apps.
            defaultCache : {
                objectDefaultTimeout           : 60,
                objectDefaultLastAccessTimeout : 30,
                useLastAccessTimeouts          : true,
                reapFrequency                  : 2,
                freeMemoryPercentageThreshold  : 0,
                evictionPolicy                 : "LRU",
                evictCount                     : 1,
                maxObjects                     : 300,
                objectStore                    : "ConcurrentStore",
                coldboxEnabled                 : true  // REQUIRED for ColdBox
            },

            caches : {
                // ColdBox event/view template caching โ€” memory-sensitive is appropriate here
                template : {
                    provider   : "coldbox.system.cache.providers.CacheBoxColdBoxProvider",
                    properties : {
                        objectDefaultTimeout           : 60,
                        objectDefaultLastAccessTimeout : 30,
                        useLastAccessTimeouts          : true,
                        reapFrequency                  : 2,
                        freeMemoryPercentageThreshold  : 0,
                        evictionPolicy                 : "LRU",
                        evictCount                     : 2,
                        maxObjects                     : 300,
                        objectStore                    : "ConcurrentSoftReferenceStore"
                    }
                },

                // Product catalog โ€” long TTL, guaranteed objects
                products : {
                    provider   : "coldbox.system.cache.providers.CacheBoxColdBoxProvider",
                    properties : {
                        objectDefaultTimeout : 240,   // 4 hours
                        maxObjects           : 1000,
                        objectStore          : "ConcurrentStore"
                    }
                },

                // Session/token cache โ€” match your session timeout
                sessions : {
                    provider   : "coldbox.system.cache.providers.CacheBoxColdBoxProvider",
                    properties : {
                        objectDefaultTimeout : 30,
                        maxObjects           : 5000,
                        objectStore          : "ConcurrentStore"
                    }
                }
            }
        };
    }
}

2c. Inline DSL inside ColdBox.cfc

// config/ColdBox.cfc
function configure() {
    // ...other config...

    // Option A: point to an external CFC
    cacheBox = { configFile : "config.CacheBox" };

    // Option B: inline DSL  (same keys as portable CFC above)
    cacheBox = {
        defaultCache : { objectDefaultTimeout : 60, maxObjects : 300, coldboxEnabled : true },
        caches       : {}
    };
}

2d. Engine-native providers (Lucee / Adobe CF caches)

// config/CacheBox.cfc  โ€” add after the default caches block
if ( listFindNoCase( "Lucee", server.coldfusion.productname ) ) {
    cacheBox.caches.luceeCache = {
        provider : "coldbox.system.cache.providers.LuceeColdBoxProvider"
    };
} else {
    cacheBox.caches.cfCache = {
        provider : "coldbox.system.cache.providers.CFColdBoxProvider"
    };
}

2e. Redis provider (cbRedis module)

box install cbRedis
// config/CacheBox.cfc
caches : {
    redis : {
        provider   : "cbRedis.models.providers.RedisProvider",
        properties : {
            host                 : getSystemSetting( "REDIS_HOST", "localhost" ),
            port                 : getSystemSetting( "REDIS_PORT", 6379 ),
            password             : getSystemSetting( "REDIS_PASSWORD", "" ),
            database             : 0,
            objectDefaultTimeout : 60,
            keyPrefix            : "myapp:"
        }
    },
    // Distribute sessions across cluster nodes
    sessions : {
        provider   : "cbRedis.models.providers.RedisProvider",
        properties : {
            host                 : getSystemSetting( "REDIS_HOST", "localhost" ),
            port                 : getSystemSetting( "REDIS_PORT", 6379 ),
            objectDefaultTimeout : 30,
            keyPrefix            : "myapp:session:"
        }
    }
}

3. Injecting Caches with WireBox

Use WireBox DSL to inject cache providers into any managed component (services, handlers, interceptors).

BoxLang:

class ProductService {

    // Inject by name โ€” resolves to the named cache provider
    @inject( "cachebox:default" )
    property name="cache";

    @inject( "cachebox:products" )
    property name="productCache";

    @inject( "cachebox:template" )
    property name="templateCache";

    // Inject the full CacheBox factory (to resolve caches dynamically)
    @inject( "cachebox" )
    property name="cacheBox";

    function getCacheByName( required string name ) {
        return cacheBox.getCache( name )
    }
}

CFML:

component {

    property name="cache"         inject="cachebox:default";
    property name="productCache"  inject="cachebox:products";
    property name="templateCache" inject="cachebox:template";
    property name="cacheBox"      inject="cachebox";
}

From handlers/interceptors (no injection needed)

// Available directly in handlers and interceptors via controller helpers
var cache    = getCache();             // default cache
var tpl      = getCache( "template" );
var cacheBox = getCacheBox();

4. Core Cache API (Same for All Providers)

All timeouts are in minutes.

// โ”€โ”€ WRITE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
cache.set( "product_1", product, 60, 30 );   // TTL=60, idle=30
cache.set( "product_1", product );           // use configured defaults
cache.set( "config", data, 0, 0 );          // eternal

// โ”€โ”€ READ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
var val = cache.get( "product_1" );          // null if missing/expired
var val = cache.getQuiet( "product_1" );    // does not reset idle timer

// โ”€โ”€ GET-OR-SET โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
var product = cache.getOrSet(
    objectKey  : "product_1",
    produce    : function() { return productRepository.findById( 1 ) },
    timeout    : 60,
    lastAccess : 30
);

// โ”€โ”€ CHECK โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
var exists = cache.lookup( "product_1" );
var exists = cache.lookupQuiet( "product_1" ); // no idle reset

// โ”€โ”€ DELETE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
cache.clear( "product_1" );
cache.clearByKeySnippet( "product_", false );        // all keys containing snippet
cache.clearByKeySnippet( "^product_\d+$", true );   // regex
cache.clearAll();
cache.expireObject( "product_1" );    // lazy expire

// โ”€โ”€ INSPECT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
var meta  = cache.getCachedObjectMetadata( "product_1" );
var keys  = cache.getKeys();
var size  = cache.getSize();
var stats = cache.getStats();

5. Service-Layer Cache Patterns

Simple cache-aside

BoxLang:

class ProductService {

    @inject( "cachebox:products" )
    property name="cache";

    function getById( required numeric id ) {
        var key     = "product_#id#"
        var product = cache.get( key )

        if ( !isNull( product ) ) {
            return product
        }

        product = productRepository.findById( id )
        cache.set( key, product, 60, 30 )
        return product
    }

    function list( numeric page = 1, numeric limit = 25 ) {
        return cache.getOrSet(
            objectKey  : "products_list_#page#_#limit#",
            produce    : function() { return productRepository.list( page = page, limit = limit ) },
            timeout    : 30,
            lastAccess : 15
        )
    }

    function save( required struct product ) {
        var saved = productRepository.save( product )
        // Write-through
        cache.set( "product_#saved.id#", saved, 60, 30 )
        // Invalidate related list caches
        cache.clearByKeySnippet( "products_list_" )
        return saved
    }

    function delete( required numeric id ) {
        productRepository.delete( id )
        cache.clear( "product_#id#" )
        cache.clearByKeySnippet( "products_list_" )
    }
}

CFML:

component {

    property name="cache" inject="cachebox:products";

    function getById( required numeric id ) {
        return cache.getOrSet(
            objectKey  : "product_#id#",
            produce    : function() { return productRepository.findById( id ) },
            timeout    : 60,
            lastAccess : 30
        )
    }
}

Stampede protection

function getExpensiveValue( required string key, required any loader, numeric timeout = 60 ) {
    var value = cache.get( key )
    if ( !isNull( value ) ) { return value }

    lock name="cache_load_#key#" timeout="15" throwontimeout="false" type="exclusive" {
        value = cache.get( key )
        if ( isNull( value ) ) {
            value = loader()
            cache.set( key, value, timeout )
        }
    }
    return value
}

6. Event Output Caching

Cache the entire output of a handler event. ColdBox stores the rendered HTML in the template cache provider.

BoxLang:

class Catalog extends coldbox.system.EventHandler {

    function index( event, rc, prc ) {
        // Cache this event's output for 60 min, idle 30 min
        event.setEventCacheableEntry(
            provider   : "template",
            timeout    : 60,
            lastAccess : 30
        )
        prc.categories = categoryService.list()
        event.setView( "catalog/index" )
    }

    function show( event, rc, prc ) {
        // Cache per product ID โ€” different cache entry per suffix
        event.setEventCacheableEntry(
            provider   : "template",
            timeout    : 120,
            lastAccess : 60,
            suffix     : rc.id ?: "0"
        )
        prc.product = productService.getById( rc.id ?: 0 )
        event.setView( "catalog/show" )
    }
}

CFML:

component extends="coldbox.system.EventHandler" {

    function index( event, rc, prc ) {
        event.setEventCacheableEntry(
            provider   : "template",
            timeout    : 60,
            lastAccess : 30
        )
        prc.categories = categoryService.list()
        event.setView( "catalog/index" )
    }
}

Invalidation: Event caches are keyed by event name + suffix. Clear them via getCache( "template" ).clearByKeySnippet( "event_name" ) or getCache( "template" ).clearAll().


7. View Fragment Caching

Cache individual view fragments inside a layout or view template. Uses the template cache.

// In a view or layout file (.bxm / .cfm)
#renderView(
    view                    = "shared/featured_products",
    cache                   = true,
    cacheTimeout            = 30,
    cacheLastAccessTimeout  = 15
)#

Fragment cache keys are auto-generated from the view path. Clear a fragment:

getCache( "template" ).clearByKeySnippet( "shared/featured_products" )

8. Query Caching (native CFML)

ColdFusion/Lucee/BoxLang natively cache queries with cachedWithin:

function getTopProducts() {
    return queryExecute(
        "SELECT * FROM products WHERE active = 1 ORDER BY views DESC LIMIT 10",
        {},
        {
            cachedWithin : createTimeSpan( 0, 0, 30, 0 ), // 30 minutes
            datasource   : "myapp"
        }
    )
}

Use CacheBox provider caching (not query-level) when you need explicit invalidation control.


9. Cache Listeners as ColdBox Interceptors

In ColdBox, CacheBox listeners are ColdBox interceptors โ€” declare them in config/ColdBox.cfc.

BoxLang:

// interceptors/CacheAuditInterceptor.bx
class CacheAuditInterceptor extends coldbox.system.Interceptor {

    function configure() {}

    // โ”€โ”€ CacheBoxProvider events โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    function afterCacheElementInsert( event, interceptData, buffer ) {
        log.debug(
            "CACHE INSERT [#interceptData.cache.getName()#] #interceptData.cacheObjectKey# ttl=#interceptData.cacheObjectTimeout#"
        )
    }

    function afterCacheElementRemoved( event, interceptData, buffer ) {
        log.debug(
            "CACHE REMOVE [#interceptData.cache.getName()#] #interceptData.cacheObjectKey#"
        )
    }

    function afterCacheElementExpired( event, interceptData, buffer ) {
        log.debug(
            "CACHE EXPIRE [#interceptData.cache.getName()#] #interceptData.cacheObjectKey#"
        )
    }

    // โ”€โ”€ CacheFactory events โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    function afterCacheFactoryConfiguration( event, interceptData, buffer ) {
        log.info( "CacheBox configured" )
        // Good place to warm caches
        warmCriticalCaches()
    }

    function beforeCacheFactoryShutdown( event, interceptData, buffer ) {
        log.warn( "CacheBox shutting down" )
    }

    private function warmCriticalCaches() {
        var cache    = getCacheBox().getCache( "products" )
        var products = productGatewayInstance.findAllActive()
        for ( var p in products ) {
            cache.set( "product_#p.id#", p, 240, 60 )
        }
    }
}

CFML:

component extends="coldbox.system.Interceptor" {

    function configure() {}

    function afterCacheElementInsert( event, interceptData, buffer ) {
        log.debug( "INSERT [#interceptData.cache.getName()#] #interceptData.cacheObjectKey#" )
    }

    function afterCacheFactoryConfiguration( event, interceptData, buffer ) {
        // Warm critical caches after CacheBox starts
    }
}

Register in config/ColdBox.cfc:

interceptors = [
    { class : "interceptors.CacheAuditInterceptor", name : "CacheAuditInterceptor", properties : {} }
];

10. CacheBox Events Available to Interceptors

CacheFactory events

EventinterceptData keys
afterCacheFactoryConfigurationcacheFactory
beforeCacheFactoryShutdowncacheFactory
afterCacheFactoryShutdowncacheFactory
afterCacheRegistrationcache
beforeCacheRemovalcache
afterCacheRemovalcache (name string)
beforeCacheReplacementoldCache, newCache
beforeCacheShutdowncache
afterCacheShutdowncache

CacheBoxProvider events

EventinterceptData keys
afterCacheElementInsertcache, cacheObject, cacheObjectKey, cacheObjectTimeout, cacheObjectLastAccessTimeout
afterCacheElementUpdatedcache, cacheNewObject, cacheOldObject
afterCacheElementRemovedcache, cacheObjectKey
afterCacheElementExpiredcache, cacheObjectKey
afterCacheClearAllcache

11. Named Cache Configuration Reference

defaultCache properties

PropertyTypeDefaultDescription
objectDefaultTimeoutnumeric60Object lifespan (minutes); 0 = eternal
objectDefaultLastAccessTimeoutnumeric30Idle timeout (minutes)
useLastAccessTimeoutsbooleantrueEnable/disable idle timeout
reapFrequencynumeric2Minutes between expiry sweeps
freeMemoryPercentageThresholdnumeric0Min JVM free memory % before caching (0=unlimited)
evictionPolicystringLRULRU, LFU, FIFO, LIFO, or custom path
evictCountnumeric1Objects evicted per policy execution
maxObjectsnumeric300Maximum live objects
objectStorestringConcurrentStoreConcurrentStore, ConcurrentSoftReferenceStore, DiskStore, JDBCStore
coldboxEnabledbooleantrueMust be true for ColdBox default cache
resetTimeoutOnAccessbooleanfalseReset TTL on each access (session-like behaviour)

12. Production Best Practices

  • Set coldboxEnabled : true in the defaultCache block โ€” required for ColdBox app integration.
  • Use the default cache for service/data caching and the template cache exclusively for event/view output.
  • Inject caches at component level via WireBox (@inject( "cachebox:name" )) โ€” never call getCache() inside methods if the component is a WireBox singleton.
  • Prefer getOrSet() at the service layer to eliminate null-check boilerplate.
  • Use getCache() / getCacheBox() inside handlers and interceptors โ€” these controller helpers spare the injection overhead for one-off use.
  • Event output caching is coarse-grained โ€” it caches entire event HTML; hard to selectively invalidate. Use only for read-heavy, infrequently updated pages.
  • View fragment caching is finer-grained than event caching โ€” prefer it over event caching when only part of a page is static.
  • Configure Redis for multi-server / auto-scaling deployments โ€” in-memory providers are per-process.
  • Namespace cache keys by domain and ID ("product_#id#", "user_#id#_prefs") to avoid cross-domain collisions.
  • Use clearByKeySnippet() to invalidate families of keys; avoid clearAll() which evicts everything and causes stampede.
  • Warm critical caches in the afterCacheFactoryConfiguration interceptor event โ€” fires after ColdBox starts CacheBox.
  • Monitor getStats() per cache to tune TTL and maxObjects; low hit rates usually mean TTL is too short or keys are not reused.
  • Never cache user-specific data with a shared key โ€” always include user identity in the cache key.