apache/druid · error · IllegalStateException

Factory [%s] not started

Error message

Factory [%s] not started

What it means

NamespaceLookupExtractorFactory.get() resolves the CacheScheduler entry for the extractor ID; if the entry is null the factory was never successfully started (start() not called, still awaiting initialization, or it failed/closed). It throws this ISE rather than returning a lookup backed by no cache, per LookupExtractorFactory contract that get() requires a started factory.

Source

Thrown at extensions-core/lookups-cached-global/src/main/java/org/apache/druid/query/lookup/NamespaceLookupExtractorFactory.java:219

  public boolean isInjective()
  {
    return injective;
  }

  // Grab the latest snapshot from the CacheScheduler's entry
  @Override
  public LookupExtractor get()
  {
    final Lock readLock = startStopSync.readLock();
    try {
      readLock.lockInterruptibly();
    }
    catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
    try {
      if (entry == null) {
        throw new ISE("Factory [%s] not started", extractorID);
      }
      final CacheScheduler.CacheState cacheState = entry.getCacheState();
      if (cacheState instanceof CacheScheduler.NoCache) {
        final String noCacheReason = ((CacheScheduler.NoCache) cacheState).name();
        throw new ISE("%s: %s, extractorID = %s", entry, noCacheReason, extractorID);
      }
      CacheScheduler.VersionedCache versionedCache = (CacheScheduler.VersionedCache) cacheState;
      final byte[] v = StringUtils.toUtf8(versionedCache.getVersion());
      final byte[] id = StringUtils.toUtf8(extractorID);
      final byte injectiveByte = isInjective() ? (byte) 1 : (byte) 0;
      final Supplier<byte[]> cacheKey = () ->
          ByteBuffer
              .allocate(CLASS_CACHE_KEY.length + id.length + 1 + v.length + 1 + 1)
              .put(CLASS_CACHE_KEY)
              .put(id).put((byte) 0xFF)
              .put(v).put((byte) 0xFF)
              .put(injectiveByte)
              .array();

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Call start() (and await its completion / use awaitInitialization) before invoking get().
  2. Verify the lookup's underlying data source is reachable so start() can finish populating the cache.
  3. Check lookup manager logs for a failed start or an earlier close() on this extractor ID; re-announce the lookup.
  4. In code, guard with isStarted()/lifecycle checks before calling get().

Example fix

// before
LookupExtractor lookup = factory.get();
// after
if (!factory.start()) { throw new ISE("lookup %s failed to start", id); }
factory.awaitInitialization();
LookupExtractor lookup = factory.get();
Defensive patterns

Strategy: validation

Validate before calling

// java
if (!factory.isStarted()) { factory.start(); }
// await initialization before get()
factory.awaitInitialization(clock, 30_000);

Try / catch

// java
try { LookupExtractor l = factory.get(); }
catch (IllegalStateException e) {
  if (e.getMessage().startsWith("Factory [") ) { /* restart/await start */ }
}

Prevention

When it happens

Trigger: Calling get() (directly or via extractor()) before start() completes; racing start() against a close/delete; factory failed during background cache loading so entry never got set.

Common situations: Lookup announced to the cluster while its underlying data (e.g. remote CSV/DB) is unreachable, so initialization never completes; tests querying lookups immediately after config load; racy lookup replacement where a delete lands between start and get.

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/457e038aa397c80c. Report an issue: GitHub.