Disclaimer: This website requires Please enable JavaScript in your browser settings for the best experience.

Dev guideRecipesAPI ReferenceChangelog
Dev guideAPI ReferenceRecipesChangelogUser GuideGitHubDev CommunityOptimizely AcademySubmit a ticketLog In
Dev guide

Configure the CMAB cache for the Go SDK

How to configure caching behavior for Contextual Multi-Armed Bandit (CMAB) decisions in the Feature Experimentation Java SDK.

👍

Beta

CMAB for Feature Experimentation is in beta. Contact your Customer Success Manager for more information.

The CMAB cache stores variation assignments to reduce latency and minimize API calls to the CMAB service.

Prerequisites

  • Java SDK version 4.3.0 or higher.
  • A CMAB-enabled experiment in your Optimizely project.

Minimum SDK version

4.3.0

Description

When a user is bucketed into a CMAB experiment, the Java SDK makes an API call to the CMAB service to determine which variation to show. To improve performance and reduce latency, the Java SDK caches these decisions based on the following:

  • User ID
  • Experiment ID
  • CMAB attribute values

The cache is automatically invalidated when CMAB attributes change for a user, ensuring fresh decisions when context changes.

By default, the SDK uses an in-memory Least Recently Used (LRU) cache with a maximum size of 10000 entries and a TTL of 30 minutes (1800 seconds). You can customize these settings or provide your own cache implementation.

Configuration options

Configure CMAB caching when building your CmabService instance using the DefaultCmabService.Builder.

Default configuration

The Java SDK uses the following default values when no custom configuration is provided:

SettingDefault ValueDescription
Cache Size10000Maximum number of cached decisions.
Cache TTL1800 seconds (30 minutes)Time-to-live for cache entries.
Cache ImplementationDefaultLRUCacheThread-safe in-memory Least Recently Used (LRU) cache.

Configuration methods

Set cache size

DefaultCmabService.builder().withCmabCacheSize(int cacheSize)

Parameters

  • cacheSize (int) – Maximum number of decisions to cache. Must be a positive integer.

Example

import com.optimizely.ab.cmab.DefaultCmabClient;
import com.optimizely.ab.cmab.service.DefaultCmabService;

// Set cache size to 500 entries
DefaultCmabService cmabService = DefaultCmabService.builder()
    .withClient(new DefaultCmabClient())
    .withCmabCacheSize(500)
    .build();

Set cache TTL

DefaultCmabService.builder().withCmabCacheTimeoutInSecs(int timeoutInSecs)

Parameters

  • timeoutInSecs (int) – Time in seconds for cache entries to live. Must be a positive number.

Example

import com.optimizely.ab.cmab.DefaultCmabClient;
import com.optimizely.ab.cmab.service.DefaultCmabService;

// Set cache TTL to 10 minutes (600 seconds)
DefaultCmabService cmabService = DefaultCmabService.builder()
    .withClient(new DefaultCmabClient())
    .withCmabCacheTimeoutInSecs(600)
    .build();

Set custom cache

DefaultCmabService.builder().withCustomCache(Cache<CmabCacheValue> cache)

Parameters

  • cache (Cache<CmabCacheValue>) – Custom cache implementation that implements the Cache interface.

Example

import com.optimizely.ab.cmab.DefaultCmabClient;
import com.optimizely.ab.cmab.service.DefaultCmabService;
import com.optimizely.ab.internal.Cache;

// Provide your own cache implementation
Cache<CmabCacheValue> customCache = new MyCustomCache<>();

DefaultCmabService cmabService = DefaultCmabService.builder()
    .withClient(new DefaultCmabClient())
    .withCustomCache(customCache)
    .build();

Examples

Basic configuration

Adjust cache size and TTL for your application's needs.

import com.optimizely.ab.Optimizely;
import com.optimizely.ab.OptimizelyFactory;
import com.optimizely.ab.OptimizelyUserContext;
import com.optimizely.ab.cmab.DefaultCmabClient;
import com.optimizely.ab.cmab.service.DefaultCmabService;
import com.optimizely.ab.config.HttpProjectConfigManager;
import com.optimizely.ab.optimizelydecision.OptimizelyDecision;

// Configure CMAB cache settings
DefaultCmabService cmabService = DefaultCmabService.builder()
    .withClient(new DefaultCmabClient())
    .withCmabCacheSize(500)           // Cache up to 500 decisions
    .withCmabCacheTimeoutInSecs(600)  // Refresh cache every 10 minutes
    .build();

// Create ProjectConfigManager
HttpProjectConfigManager configManager = HttpProjectConfigManager.builder()
    .withSdkKey("your-sdk-key")
    .build();

// Create Optimizely client with custom CMAB service
Optimizely optimizelyClient = OptimizelyFactory.newDefaultInstance(
    configManager,
    null,  // notification center
    null,  // event handler
    null,  // odp api manager
    cmabService
);

// Use the client normally
Map<String, Object> attributes = new HashMap<>();
attributes.put("age", 25);
attributes.put("location", "US");

OptimizelyUserContext user = optimizelyClient.createUserContext("user123", attributes);
OptimizelyDecision decision = user.decide("my-cmab-flag");
// Cache will store this decision for 10 minutes

Advanced: custom cache implementation

For multi-instance deployments or distributed systems, you can provide your own cache implementation.

import com.optimizely.ab.Optimizely;
import com.optimizely.ab.OptimizelyFactory;
import com.optimizely.ab.OptimizelyUserContext;
import com.optimizely.ab.cmab.DefaultCmabClient;
import com.optimizely.ab.cmab.service.CmabCacheValue;
import com.optimizely.ab.cmab.service.DefaultCmabService;
import com.optimizely.ab.config.HttpProjectConfigManager;
import com.optimizely.ab.internal.Cache;
import com.optimizely.ab.optimizelydecision.OptimizelyDecision;

import java.util.HashMap;
import java.util.Map;

// Example: Custom cache implementation for distributed systems
// You can use Redis, Memcached, or any other caching system
class CustomCmabCache implements Cache<CmabCacheValue> {
    
    public CustomCmabCache() {
        // Initialize your cache backend here
        // For example: this.redis = new JedisPool(), this.memcached = new MemcachedClient(), etc.
    }
    
    @Override
    public CmabCacheValue lookup(String key) {
        // Retrieve value from your cache backend
        // Return the cached value if found, null otherwise
        // Example: String json = redis.get(key); return deserialize(json);
        return null;
    }
    
    @Override
    public void save(String key, CmabCacheValue value) {
        // Store the key-value pair in your cache backend
        // Consider setting an expiration/TTL if your backend supports it
        // Example: redis.setex(key, ttl, serialize(value));
    }
    
    @Override
    public void remove(String key) {
        // Remove the key from your cache backend
        // Example: redis.del(key);
    }
    
    @Override
    public void reset() {
        // Clear all CMAB cache entries from your cache backend
        // Example: redis.flushDB() or delete keys matching a pattern
    }
}

// Create and configure your custom cache
Cache<CmabCacheValue> customCache = new CustomCmabCache();

// Configure CMAB service with custom cache
DefaultCmabService cmabService = DefaultCmabService.builder()
    .withClient(new DefaultCmabClient())
    .withCustomCache(customCache)
    .build();

// Create ProjectConfigManager
HttpProjectConfigManager configManager = HttpProjectConfigManager.builder()
    .withSdkKey("YOUR_SDK_KEY")
    .build();

// Create Optimizely client with custom CMAB service
Optimizely optimizelyClient = OptimizelyFactory.newDefaultInstance(
    configManager,
    null,
    null,
    null,
    cmabService
);

// CMAB decisions now use your custom cache
Map<String, Object> attributes = new HashMap<>();
attributes.put("device_type", "mobile");

OptimizelyUserContext user = optimizelyClient.createUserContext("user123", attributes);
OptimizelyDecision decision = user.decide("my-cmab-flag");

Cache behavior

Automatic cache invalidation

The cache is automatically invalidated when the following occurs:

  • The cached entry's TTL expires.
  • CMAB attribute values change for a user (detected through MD5 hash comparison).
  • IGNORE_CMAB_CACHE decide option is used.
  • INVALIDATE_USER_CMAB_CACHE decide option is used.
  • RESET_CMAB_CACHE decide option is used.

CMAB-specific decide options

Control cache behavior on a per-decision basis using decide options.

import com.optimizely.ab.OptimizelyUserContext;
import com.optimizely.ab.optimizelydecision.OptimizelyDecideOption;
import com.optimizely.ab.optimizelydecision.OptimizelyDecision;
import java.util.Arrays;

// Ignore cache for this decision (always fetch fresh)
OptimizelyDecision decision = user.decide(
    "my-flag",
    Arrays.asList(OptimizelyDecideOption.IGNORE_CMAB_CACHE)
);

// Invalidate cached decision for this user and experiment
decision = user.decide(
    "my-flag",
    Arrays.asList(OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE)
);

// Clear entire CMAB cache
decision = user.decide(
    "my-flag",
    Arrays.asList(OptimizelyDecideOption.RESET_CMAB_CACHE)
);

// Combine multiple options
decision = user.decide(
    "my-flag",
    Arrays.asList(
        OptimizelyDecideOption.INCLUDE_REASONS,
        OptimizelyDecideOption.IGNORE_CMAB_CACHE
    )
);

Available CMAB decideoOptions

Decide OptionDescription
IGNORE_CMAB_CACHEBypass cache and fetch a fresh decision from CMAB service.
INVALIDATE_USER_CMAB_CACHERemove cached decision for this user and experiment before deciding.
RESET_CMAB_CACHEClear all entries from the CMAB cache before deciding.

When to use a custom cache

Consider implementing a custom cache when

  • Multi-instance deployments – Share cache across multiple application instances using Redis, Memcached, or similar.
  • Distributed systems – Use external caching systems for centralized caching across services.
  • Persistent cache – Maintain cache across application restarts.
  • Custom eviction policies – Implement domain-specific cache management (for instance, user-based eviction).
  • Monitoring – Track cache metrics (hit rate, memory usage, and so on) with your existing monitoring tools.
  • Compliance – Store cached data in specific locations or with specific encryption requirements.