{"record":{"id":"2b611a8b99986bed","repo":"apache/druid","slug":"given-update-for-lookup-s-s-can-t-replace-ex","errorCode":null,"errorMessage":"given update for lookup [%s]:[%s] can't replace existing spec [%s].","messagePattern":"given update for lookup \\[(.+?)\\]:\\[(.+?)\\] can't replace existing spec \\[(.+?)\\]\\.","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"server/src/main/java/org/apache/druid/server/lookup/cache/LookupCoordinatorManager.java","lineNumber":233,"sourceCode":"        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);\n          } else {\n            // Update existing tier\n            final Map<String, LookupExtractorFactoryMapContainer> updatedTierSpec = new HashMap<>(priorTierSpec);\n\n            for (Map.Entry<String, LookupExtractorFactoryMapContainer> e : updateTierSpec.entrySet()) {\n              if (updatedTierSpec.containsKey(e.getKey()) && !e.getValue().replaces(updatedTierSpec.get(e.getKey()))) {\n                throw new IAE(\n                    \"given update for lookup [%s]:[%s] can't replace existing spec [%s].\",\n                    tier,\n                    e.getKey(),\n                    updatedTierSpec.get(e.getKey())\n                );\n              }\n            }\n            updatedTierSpec.putAll(updateTierSpec);\n            updatedSpec.put(tier, updatedTierSpec);\n          }\n        }\n      }\n      return configManager.set(LOOKUP_CONFIG_KEY, updatedSpec, auditInfo).isOk();\n    }\n  }\n\n  public Map<String, Map<String, LookupExtractorFactoryMapContainer>> getKnownLookups()\n  {","sourceCodeStart":215,"sourceCodeEnd":251,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/server/src/main/java/org/apache/druid/server/lookup/cache/LookupCoordinatorManager.java#L215-L251","documentation":"When updating lookups in an existing tier, each new lookup spec must semantically replace the currently stored spec. LookupExtractorFactoryMapContainer.replaces() enforces monotonic versioning; if the submitted spec cannot replace the existing one (e.g. an equal or older version), updateLookups throws this IllegalArgumentException naming the tier, lookup key, and existing spec.","triggerScenarios":"POSTing an update for lookup tier:lookupName whose spec has the same or lower 'version' field than the existing spec, or is otherwise judged non-replacing by the container's replaces() comparison.","commonSituations":"Re-running an automation script that re-posts the same spec with the same version; CI replaying stale config; editing a lookup without bumping its 'version' field.","solutions":["Bump the 'version' field in the lookup spec to a value greater than the existing spec and re-POST.","If the change is unintentional, skip re-posting an identical spec.","Delete the lookup first (DELETE endpoint) if you truly need to reset it, then create it anew."],"exampleFix":"// before\n{\"dataSource\": \"x\", \"version\": \"2023-01-01T00:00:00.000Z\", ...}\n// after\n{\"dataSource\": \"x\", \"version\": \"2024-06-01T00:00:00.000Z\", ...}","handlingStrategy":"validation","validationCode":"function isNewerVersion(newSpec, existingSpec) {\n  return String(newSpec.version) > String(existingSpec.version);\n}\nif (!isNewerVersion(incoming, existing)) {\n  throw new Error(`bump version: incoming ${incoming.version} <= existing ${existing.version}`);\n}","typeGuard":"function canReplace(incoming, existing) {\n  return incoming && existing && new Date(incoming.version) > new Date(existing.version);\n}","tryCatchPattern":"try {\n  await updateLookups(tier, lookupName, spec);\n} catch (IAE e) {\n  if (e.getMessage().contains(\"can't replace existing spec\")) {\n    log.warn(`Skipping stale update for ${lookupName}; bump the version field`);\n  } else { throw e; }\n}","preventionTips":["Always bump the 'version' field (timestamp-style string) whenever editing a lookup spec.","Make automation idempotent: skip re-posting when the spec is unchanged.","Fetch the current spec first and compare versions before submitting an update."],"tags":["lookups","versioning","rest-api","validation"],"backgroundTag":"conflicting-update-version","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"}