Skip to content

Caching: Memory, Redis, or Off

Resolved permissions are cached so the three-tier resolution (roles → groups → hierarchy → merge) doesn’t run on every request. The store is a config choice with a safe default.

Store Config When
Memory (default) iam.cache.store: memory Single instance, dev, most apps. Caffeine-backed, in-process, zero infra
Redis iam.cache.store: redis Multiple app instances that must share invalidation
Off iam.cache.enabled: false Debugging, tests, or when resolution must always be fresh
iam:
cache:
store: memory # already the default
permissions-ttl: 5m

Per-instance Caffeine cache. In a multi-node deployment each node caches independently — a permission change invalidates immediately on the node that made it and within the TTL on others. If that window matters, use Redis.

iam:
cache:
store: redis
spring:
data:
redis:
host: redis.internal
port: 6379
// Redis is opt-in — the starter does not ship it:
implementation("org.springframework.boot:spring-boot-starter-data-redis")

Keys: iam:permissions:{tenantId}:{userId}, TTL from permissions-ttl. All instances share one cache and see invalidations instantly.

iam:
cache:
enabled: false

Wires a no-op cache — every request resolves permissions fresh. Correct but the most expensive option; use deliberately.

PermissionCache is a public seam. Provide your own bean and IAM steps aside:

@Bean
fun permissionCache(hazelcast: HazelcastInstance): PermissionCache =
HazelcastPermissionCache(hazelcast)

IAM evicts affected entries on every permission-changing operation (role grants, permission edits, group membership). The classic interview question — “how stale can a permission be?” — has a precise answer here: at most permissions-ttl on nodes other than the one that made the change (memory store), or zero (Redis store). Revoking access hard-fails faster than the TTL only if you also revoke the session/token.