apache/druid · error · IllegalArgumentException

given update for lookup

Error message

given update for lookup [%s]:[%s] can't replace existing spec [%s].

What it means

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.

Solutions

  1. Bump the 'version' field in the lookup spec to a value greater than the existing spec and re-POST.
  2. If the change is unintentional, skip re-posting an identical spec.
  3. Delete the lookup first (DELETE endpoint) if you truly need to reset it, then create it anew.

Example fix

// before
{"dataSource": "x", "version": "2023-01-01T00:00:00.000Z", ...}
// after
{"dataSource": "x", "version": "2024-06-01T00:00:00.000Z", ...}
Defensive patterns

Strategy: validation

Validate before calling

function isNewerVersion(newSpec, existingSpec) {
  return String(newSpec.version) > String(existingSpec.version);
}
if (!isNewerVersion(incoming, existing)) {
  throw new Error(`bump version: incoming ${incoming.version} <= existing ${existing.version}`);
}

Type guard

function canReplace(incoming, existing) {
  return incoming && existing && new Date(incoming.version) > new Date(existing.version);
}

Try / catch

try {
  await updateLookups(tier, lookupName, spec);
} catch (IAE e) {
  if (e.getMessage().contains("can't replace existing spec")) {
    log.warn(`Skipping stale update for ${lookupName}; bump the version field`);
  } else { throw e; }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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

Appendix: source

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

        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);
          } else {
            // Update existing tier
            final Map<String, LookupExtractorFactoryMapContainer> updatedTierSpec = new HashMap<>(priorTierSpec);

            for (Map.Entry<String, LookupExtractorFactoryMapContainer> e : updateTierSpec.entrySet()) {
              if (updatedTierSpec.containsKey(e.getKey()) && !e.getValue().replaces(updatedTierSpec.get(e.getKey()))) {
                throw new IAE(
                    "given update for lookup [%s]:[%s] can't replace existing spec [%s].",
                    tier,
                    e.getKey(),
                    updatedTierSpec.get(e.getKey())
                );
              }
            }
            updatedTierSpec.putAll(updateTierSpec);
            updatedSpec.put(tier, updatedTierSpec);
          }
        }
      }
      return configManager.set(LOOKUP_CONFIG_KEY, updatedSpec, auditInfo).isOk();
    }
  }

  public Map<String, Map<String, LookupExtractorFactoryMapContainer>> getKnownLookups()
  {

View on GitHub (pinned to 9b90983fd2)