apache/druid · error · IllegalStateException

Cannot get cache: %s

Error message

Cannot get cache: %s

What it means

CacheScheduler.Entry.getCache() returns the materialized lookup map only while the entry's CacheState is a VersionedCache (a successfully loaded snapshot). If the entry is in any other state — e.g. it was deleted, or the cache creation failed — there is no cache to return and this ISE is thrown with the current state.

Source

Thrown at extensions-core/lookups-cached-global/src/main/java/org/apache/druid/server/lookup/namespace/cache/CacheScheduler.java:104

     * Returns the last cache state, either {@link NoCache} or {@link VersionedCache}.
     */
    public CacheState getCacheState()
    {
      return impl.cacheStateHolder.get();
    }

    /**
     * @return the entry's cache if it is already initialized and not yet closed
     * @throws IllegalStateException if the entry's cache is not yet initialized, or {@link #close()} has
     * already been called
     */
    public Map<String, String> getCache()
    {
      CacheState cacheState = getCacheState();
      if (cacheState instanceof VersionedCache) {
        return ((VersionedCache) cacheState).getCache();
      } else {
        throw new ISE("Cannot get cache: %s", cacheState);
      }
    }

    @VisibleForTesting
    Future<?> getUpdaterFuture()
    {
      return impl.updaterFuture;
    }

    @VisibleForTesting
    public void awaitTotalUpdates(int totalUpdates) throws InterruptedException
    {
      impl.updateCounter.awaitCount(totalUpdates);
    }

    @VisibleForTesting
    public void awaitTotalUpdatesWithTimeout(int totalUpdates, long timeoutMills)
        throws InterruptedException, TimeoutException

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Check entry lifecycle before reading: only call getCache() on a live, scheduled entry.
  2. Use the Entry's cacheFuture/getUpdaterFuture or await the update future before reading the cache.
  3. Fix code to re-fetch the entry from the scheduler after delete/replace instead of caching the reference.

Example fix

// before
Entry entry = scheduler.schedule(ns);
scheduler.delete(entry);
Map<String,String> m = entry.getCache(); // ISE
// after
Entry entry = scheduler.schedule(ns);
Map<String,String> m = entry.getCache(); // read while live
scheduler.delete(entry);
Defensive patterns

Strategy: try-catch

Validate before calling

if (entry.getCacheState() instanceof VersionedCache) { entry.getCache(); }

Type guard

boolean readable(Entry e) { return e.getCacheState() instanceof VersionedCache; }

Try / catch

try { return entry.getCache(); } catch (IllegalStateException e) { if (e.getMessage().startsWith("Cannot get cache:")) { return Collections.emptyMap(); } throw e; }

Prevention

When it happens

Trigger: Calling entry.getCache() after the entry has been closed/deleted via scheduler.delete(entry), or before/since an update replaced the versioned snapshot with a non-VersionedCache state.

Common situations: Concurrent lookup deletion while another thread reads the cache; using a stale Entry reference after shutdown; tests driving entry lifecycle without waiting for load completion.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/26a594e95defa0ab. Report an issue: GitHub.