apache/druid · error · IllegalStateException

Not initialized. If this is the first lookup, post an empty…

Error message

Not initialized. If this is the first lookup, post an empty map to initialize

What it means

LookupCoordinatorManager.updateLookups refuses to apply a non-empty lookup update when the coordinator's cache of known lookups is not yet initialized (getKnownLookups() returned null). This guard prevents a client from accidentally wiping all existing lookup configuration because the manager simply hasn't loaded it yet. The first update must be an empty map to explicitly initialize the lookup state.

Solutions

  1. POST an empty JSON object {} to /druid/coordinator/v1/lookups (with the tier/layer envelope) once to initialize.
  2. Then POST the real lookup specs in a second request.
  3. If the coordinator was restarted and should already be initialized, check why getKnownLookups() is null (config load errors in logs).

Example fix

// before
client.post("/druid/coordinator/v1/lookups", myLookupSpecs);
// after
client.post("/druid/coordinator/v1/lookups", "{}");          // initialize once
client.post("/druid/coordinator/v1/lookups", myLookupSpecs);  // then update
Defensive patterns

Strategy: validation

Validate before calling

// Initialize once before any lookup updates:
await fetch(`${coordinator}/druid/coordinator/v1/lookups`, {
  method: 'POST', body: '{}'
}).then(r => { if (!r.ok) throw new Error('lookup init failed'); });

Type guard

function isInitialized(knownLookups) {
  return knownLookups !== null && knownLookups !== undefined;
}

Try / catch

try {
  await updateLookups(specs);
} catch (ISE e) {
  if (e.getMessage().startsWith("Not initialized")) {
    await postEmptyInitMap();
    await updateLookups(specs);
  } else { throw e; }
}

Prevention

When it happens

Trigger: POSTing lookup specs to /druid/coordinator/v1/lookups (or calling updateLookups) before any initialization POST of an empty map {} has been made, so priorSpec is null while updateSpec is non-empty.

Common situations: Fresh cluster or fresh lookup tier where operators push their lookup tables without the required initial empty-map POST; coordinator restarted and config load failed leaving cache null; automation scripts skipping the init step.

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

Appendix: source

Thrown at server/src/main/java/org/apache/druid/server/lookup/cache/LookupCoordinatorManager.java:208

      return true;
    }

    //ensure all the lookups specs have version specified. ideally this should be done in the LookupExtractorFactoryMapContainer
    //constructor but that allows null to enable backward compatibility with 0.10.0 lookup specs
    for (final Map.Entry<String, Map<String, LookupExtractorFactoryMapContainer>> tierEntry : updateSpec.entrySet()) {
      for (Map.Entry<String, LookupExtractorFactoryMapContainer> e : tierEntry.getValue().entrySet()) {
        Preconditions.checkNotNull(
            e.getValue().getVersion(),
            "lookup [%s]:[%s] does not have version.", tierEntry.getKey(), e.getKey()
        );
      }
    }

    synchronized (this) {
      final Map<String, Map<String, LookupExtractorFactoryMapContainer>> priorSpec = getKnownLookups();
      if (priorSpec == null && !updateSpec.isEmpty()) {
        // To prevent accidentally erasing configs if we haven't updated our cache of the values
        throw new ISE("Not initialized. If this is the first lookup, post an empty map to initialize");
      }
      final Map<String, Map<String, LookupExtractorFactoryMapContainer>> updatedSpec;

      // Only add or update here, don't delete.
      if (priorSpec == null) {
        // all new
        updatedSpec = updateSpec;
      } else {
        // Needs update
        updatedSpec = new HashMap<>(priorSpec);
        for (final Map.Entry<String, Map<String, LookupExtractorFactoryMapContainer>> tierEntry : updateSpec.entrySet()) {
          final String tier = tierEntry.getKey();
          final Map<String, LookupExtractorFactoryMapContainer> updateTierSpec = tierEntry.getValue();
          final Map<String, LookupExtractorFactoryMapContainer> priorTierSpec = priorSpec.get(tier);

          if (priorTierSpec == null) {
            // New tier
            updatedSpec.put(tier, updateTierSpec);

View on GitHub (pinned to 9b90983fd2)