quarkusio/quarkus · error · IllegalStateException

The write-based expiration policy can only be changed if the

Error message

The write-based expiration policy can only be changed if the cache was constructed with an expire-after-write configuration value

What it means

CaffeineCacheImpl.setExpireAfterWrite only works when the cache was created with an expire-after-write value; Caffeine exposes the write-based FixedExpiration policy only if so configured. If the policy is absent, changing it is meaningless, so an IllegalStateException is thrown.

Source

Thrown at extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/caffeine/CaffeineCacheImpl.java:301

    @Override
    public Set<Object> keySet() {
        return Set.copyOf(cache.asMap().keySet());
    }

    @SuppressWarnings("unchecked")
    @Override
    public <V> void put(Object key, CompletableFuture<V> valueFuture) {
        cache.put(key, (CompletableFuture<Object>) valueFuture);
    }

    @Override
    public void setExpireAfterWrite(Duration duration) {
        Optional<FixedExpiration<Object, Object>> fixedExpiration = cache.synchronous().policy().expireAfterWrite();
        if (fixedExpiration.isPresent()) {
            fixedExpiration.get().setExpiresAfter(duration);
            cacheInfo.expireAfterWrite = duration;
        } else {
            throw new IllegalStateException("The write-based expiration policy can only be changed if the cache was " +
                    "constructed with an expire-after-write configuration value");
        }
    }

    @Override
    public void setExpireAfterAccess(Duration duration) {
        Optional<FixedExpiration<Object, Object>> fixedExpiration = cache.synchronous().policy().expireAfterAccess();
        if (fixedExpiration.isPresent()) {
            fixedExpiration.get().setExpiresAfter(duration);
            cacheInfo.expireAfterAccess = duration;
        } else {
            throw new IllegalStateException("The access-based expiration policy can only be changed if the cache was " +
                    "constructed with an expire-after-access configuration value");
        }
    }

    @Override
    public void setMaximumSize(long maximumSize) {

View on GitHub (pinned to e1c734241f)

Solutions

  1. Add expire-after-write to the cache config (quarkus.cache.caffeine.<name>.expire-after-write)
  2. Only call setExpireAfterWrite for caches known to be write-expiring (check getCacheInfo)
  3. Rebuild/recreate the cache with the desired policy if runtime changes are required

Example fix

// before (application.properties has no expire-after-write)
cache.setExpireAfterWrite(Duration.ofSeconds(30));
// after
# application.properties
quarkus.cache.caffeine.myCache.expire-after-write=30S
Defensive patterns

Strategy: validation

Validate before calling

if (cache.getCacheInfo().expireAfterWrite == null) {
    throw new IllegalStateException("configure expire-after-write before calling setExpireAfterWrite");
}

Try / catch

try {
    cache.setExpireAfterWrite(duration);
} catch (IllegalStateException e) {
    log.warn("cache lacks write expiration policy; update application.properties", e);
}

Prevention

When it happens

Trigger: Calling setExpireAfterWrite (e.g. from testConfig during tests) on a cache whose Quarkus config lacks quarkus.cache.caffeine.<name>.expire-after-write.

Common situations: Tests that programmatically tune expiration without declaring the TTL in application.properties; caches configured only with maximum-size or expire-after-access.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/1c775f4711e0419e. Report an issue: GitHub.