Skip to main content

Caching

The Java SDK supports caching to reduce API calls and improve performance.

Caffeine Cache​

Caffeine provides high-performance in-memory caching, ideal for single-instance applications.

Installation​

<dependency>
<groupId>io.toggly</groupId>
<artifactId>toggly-cache-caffeine</artifactId>
<version>1.5.1</version>
</dependency>

Configuration​

SnapshotProvider httpProvider = new HttpSnapshotProvider(config);

SnapshotProvider cachedProvider = new CaffeineCachingSnapshotProvider(
httpProvider,
CaffeineCacheConfig.builder()
.expireAfterWrite(Duration.ofMinutes(5))
.refreshAfterWrite(Duration.ofMinutes(1))
.maximumSize(100)
.recordStats()
.build()
);

TogglyClient client = new TogglyClient(config, cachedProvider);

The examples assume an existing TogglyConfig config. Keep the client/provider for the application's lifetime. Because the provider was supplied to the client, close client first and cachedProvider second at shutdown; the outer cache closes the HTTP delegate. HTTP polling/live updates do not evict this outer snapshot cache, so its TTL/refresh policy can delay the definitions visible to evaluation. Call the outer provider's refresh() to fetch through it explicitly.

Configuration Options​

OptionDefaultDescription
expireAfterWrite-Time after which entries expire
refreshAfterWrite-Time after which entries are refreshed asynchronously
maximumSize0 (unlimited)Maximum number of entries
recordStatsfalseEnable statistics recording

Accessing Statistics​

CaffeineCachingSnapshotProvider provider = ...;
CacheStats stats = provider.stats();

System.out.println("Hit rate: " + stats.hitRate());
System.out.println("Miss rate: " + stats.missRate());
System.out.println("Load count: " + stats.loadCount());

Evaluation Cache​

Definition caching keeps evaluation sensitive to the current request context. Only add a result cache if stale answers are acceptable for its TTL. Its key uses the feature, identity and context hash (including claims/request/entity), but no definition revision or current time. A refresh or TimeWindow boundary therefore does not automatically invalidate an answer.

Use a short TTL and explicitly invalidate from application code when you know definitions have changed. TogglyClient.onRefresh does not currently invoke listeners; it cannot wire automatic invalidation. The following cache must be long-lived, and every evaluation must pass the complete context:

CaffeineEvaluationCache evaluationCache = CaffeineEvaluationCache.builder()
.expireAfterWrite(Duration.ofSeconds(10))
.maximumSize(10000)
.recordStats()
.build();

// Use cached evaluation
boolean enabled = evaluationCache.getOrCompute(
"my-feature",
context,
() -> togglyClient.isEnabled("my-feature", context)
);

// Invalidate when features change
evaluationCache.invalidateFeature("my-feature");
evaluationCache.invalidateAll();

Redis Cache​

Redis provides distributed caching, ideal for multi-instance deployments.

Installation​

<dependency>
<groupId>io.toggly</groupId>
<artifactId>toggly-cache-redis</artifactId>
<version>1.5.1</version>
</dependency>

Configuration​

SnapshotProvider redisProvider = new RedisCachingSnapshotProvider(
new HttpSnapshotProvider(config),
RedisCacheConfig.builder()
.host("localhost")
.port(6379)
.ttl(Duration.ofMinutes(5))
.build()
);

TogglyClient client = new TogglyClient(config, redisProvider);

Configuration Options​

OptionDefaultDescription
hostlocalhostRedis host
port6379Redis port
passwordnullRedis password
database0Redis database number
keyPrefixtoggly:Prefix for cache keys
ttl5 minutesTime-to-live for cached entries
timeout2000Connection timeout in milliseconds
sslfalseEnable SSL/TLS

Close the client before redisProvider at shutdown. The Redis wrapper closes its HTTP delegate and any Jedis pool it created itself. An existing pool remains caller-owned. Do not close a shared pool from each client.

Cache Invalidation​

RedisCachingSnapshotProvider provider = ...;

// Invalidate the cache
provider.invalidate();

Spring Boot Integration​

Choose one of these configurations in your component-scanned package. It replaces the plain HTTP provider bean from the Spring setup, not the client. TogglyConfig is the SDK type; FeatureCacheConfiguration is your class. Spring closes the outer provider, which closes its HTTP delegate. The Redis tab requires application properties spring.redis.host and spring.redis.port.

import io.toggly.core.config.TogglyConfig;
import io.toggly.core.snapshot.HttpSnapshotProvider;
import io.toggly.core.snapshot.SnapshotProvider;
import io.toggly.cache.caffeine.CaffeineCacheConfig;
import io.toggly.cache.caffeine.CaffeineCachingSnapshotProvider;
import java.time.Duration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeatureCacheConfiguration {

@Bean(destroyMethod = "close")
public SnapshotProvider snapshotProvider(TogglyConfig config) {
return new CaffeineCachingSnapshotProvider(
new HttpSnapshotProvider(config),
CaffeineCacheConfig.builder()
.expireAfterWrite(Duration.ofMinutes(5))
.refreshAfterWrite(Duration.ofMinutes(1))
.recordStats()
.build()
);
}
}

Caching Strategies​

1. Cache-Aside (Default)​

The SDK checks cache first, fetches from API on miss:

// Automatic cache-aside behavior
boolean enabled = client.isEnabled("my-feature");
// 1. Check cache
// 2. If miss, fetch from API
// 3. Store in cache
// 4. Return result

2. Write-Through​

Cache is updated when refreshing:

// Fetch definitions and update this local cache (does not edit dashboard flags)
client.refresh();

3. Background Refresh​

Caffeine can refresh entries asynchronously:

CaffeineCacheConfig.builder()
.expireAfterWrite(Duration.ofMinutes(10))
.refreshAfterWrite(Duration.ofMinutes(1)) // Async refresh
.build();

Best Practices​

  1. Use appropriate TTL: Balance freshness vs. performance
  2. Monitor cache stats: Track hit rates to optimize settings
  3. Use Redis for distributed: When running multiple instances
  4. Prefer definition caching: Result caches can retain stale targeting decisions
  5. Handle cache failures gracefully: SDK falls back to API

Reliability (signed snapshots)​

When signed definitions are enabled, snapshots store exact server defs JSON (signedDefsJson) for verification — never re-serialize models to verify. Configure onError on TogglyConfig.Builder, and call clearCache() to drop persisted feature/JWKS snapshots after corruption or key rotation.

WebSocket signing-key-updated clears JWKS and forces a definitions refresh (see Live updates).

Full signed-definitions setup: Signed definitions.

See Server-side reliability.

Cache Warming​

Attempt to pre-fetch definitions at startup. The callback in your TogglyConfig reports failures; completion of this listener is not proof of a fresh snapshot:

@Component
public class CacheWarmer implements ApplicationListener<ApplicationReadyEvent> {

@Autowired
private TogglyClient togglyClient;

@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// Attempt to warm the cache
togglyClient.refresh();
}
}