{"record":{"id":"fa11245aa5e2114f","repo":"apache/druid","slug":"not-initialized-if-this-is-the-first-lookup-post","errorCode":null,"errorMessage":"Not initialized. If this is the first lookup, post an empty map to initialize","messagePattern":"Not initialized\\. If this is the first lookup, post an empty map to initialize","errorType":"validation","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"server/src/main/java/org/apache/druid/server/lookup/cache/LookupCoordinatorManager.java","lineNumber":208,"sourceCode":"      return true;\n    }\n\n    //ensure all the lookups specs have version specified. ideally this should be done in the LookupExtractorFactoryMapContainer\n    //constructor but that allows null to enable backward compatibility with 0.10.0 lookup specs\n    for (final Map.Entry<String, Map<String, LookupExtractorFactoryMapContainer>> tierEntry : updateSpec.entrySet()) {\n      for (Map.Entry<String, LookupExtractorFactoryMapContainer> e : tierEntry.getValue().entrySet()) {\n        Preconditions.checkNotNull(\n            e.getValue().getVersion(),\n            \"lookup [%s]:[%s] does not have version.\", tierEntry.getKey(), e.getKey()\n        );\n      }\n    }\n\n    synchronized (this) {\n      final Map<String, Map<String, LookupExtractorFactoryMapContainer>> priorSpec = getKnownLookups();\n      if (priorSpec == null && !updateSpec.isEmpty()) {\n        // To prevent accidentally erasing configs if we haven't updated our cache of the values\n        throw new ISE(\"Not initialized. If this is the first lookup, post an empty map to initialize\");\n      }\n      final Map<String, Map<String, LookupExtractorFactoryMapContainer>> updatedSpec;\n\n      // Only add or update here, don't delete.\n      if (priorSpec == null) {\n        // all new\n        updatedSpec = updateSpec;\n      } else {\n        // Needs update\n        updatedSpec = new HashMap<>(priorSpec);\n        for (final Map.Entry<String, Map<String, LookupExtractorFactoryMapContainer>> tierEntry : updateSpec.entrySet()) {\n          final String tier = tierEntry.getKey();\n          final Map<String, LookupExtractorFactoryMapContainer> updateTierSpec = tierEntry.getValue();\n          final Map<String, LookupExtractorFactoryMapContainer> priorTierSpec = priorSpec.get(tier);\n\n          if (priorTierSpec == null) {\n            // New tier\n            updatedSpec.put(tier, updateTierSpec);","sourceCodeStart":190,"sourceCodeEnd":226,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/server/src/main/java/org/apache/druid/server/lookup/cache/LookupCoordinatorManager.java#L190-L226","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["POST an empty JSON object {} to /druid/coordinator/v1/lookups (with the tier/layer envelope) once to initialize.","Then POST the real lookup specs in a second request.","If the coordinator was restarted and should already be initialized, check why getKnownLookups() is null (config load errors in logs)."],"exampleFix":"// before\nclient.post(\"/druid/coordinator/v1/lookups\", myLookupSpecs);\n// after\nclient.post(\"/druid/coordinator/v1/lookups\", \"{}\");          // initialize once\nclient.post(\"/druid/coordinator/v1/lookups\", myLookupSpecs);  // then update","handlingStrategy":"validation","validationCode":"// Initialize once before any lookup updates:\nawait fetch(`${coordinator}/druid/coordinator/v1/lookups`, {\n  method: 'POST', body: '{}'\n}).then(r => { if (!r.ok) throw new Error('lookup init failed'); });","typeGuard":"function isInitialized(knownLookups) {\n  return knownLookups !== null && knownLookups !== undefined;\n}","tryCatchPattern":"try {\n  await updateLookups(specs);\n} catch (ISE e) {\n  if (e.getMessage().startsWith(\"Not initialized\")) {\n    await postEmptyInitMap();\n    await updateLookups(specs);\n  } else { throw e; }\n}","preventionTips":["Always perform the one-time empty-map POST to /druid/coordinator/v1/lookups before pushing real lookup specs.","In automation, add an idempotent 'ensure initialized' step that posts {} when the manager is fresh.","Check coordinator logs for lookup config load failures after restarts that can leave the cache null."],"tags":["lookups","initialization","rest-api","state"],"backgroundTag":"invalid-state-transition","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}