apache/druid · error · IllegalStateException

destroy method returned false for lookup

Error message

destroy method returned false for lookup [%s]:[%s]

What it means

dropContainer() removes a lookup: after logging, it calls the container's LookupExtractorFactory.destroy(). If destroy() returns false the lookup could not be torn down cleanly, so an IllegalStateException is thrown naming the lookup and container.

Solutions

  1. Ensure the factory is stopped/closed appropriately before destroy, or that destroy() is idempotent in the factory implementation.
  2. Check factory logs for the underlying reason destroy() returned false.
  3. Retry the drop after resources are released (the notice will be reprocessed or reissued).
  4. If a custom factory, fix destroy() to release resources or return true when already destroyed.

Example fix

// before: custom factory destroy always false while started
public boolean destroy() { if (started) { return false; } ... }
// after: stop then destroy
public boolean destroy() { if (started) { stop(); } resources.release(); return true; }
Defensive patterns

Strategy: try-catch

Try / catch

try { manager.remove(name, container); } catch (IllegalStateException e) { if (e.getMessage().startsWith("destroy method returned false")) { LOG.warn("lookup %s not cleanly destroyed; will retry", name); retryDropLater(name, container); } else { throw e; } }

Prevention

When it happens

Trigger: handle() processing a DropNotice calls dropContainer(); the factory's destroy() returns false, e.g., the factory refuses to destroy because it is still started/in-use or its backing resources can't be released.

Common situations: Lookups still referenced/started when dropped; external store connection failure preventing resource cleanup; factory implementations returning false on non-fatal cleanup issues.

Related errors


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

Appendix: source

Thrown at server/src/main/java/org/apache/druid/query/lookup/LookupReferencesManager.java:610

  private LookupUpdateState atomicallyUpdateStateRef(Function<LookupUpdateState, LookupUpdateState> fn)
  {
    while (true) {
      LookupUpdateState old = stateRef.get();
      LookupUpdateState newState = fn.apply(old);
      if (stateRef.compareAndSet(old, newState)) {
        return newState;
      }
    }
  }

  private void dropContainer(LookupExtractorFactoryContainer container, String lookupName)
  {
    if (container != null) {
      LOG.debug("Removed lookup [%s] with spec [%s].", lookupName, container);

      if (!container.getLookupExtractorFactory().destroy()) {
        throw new ISE(
            "destroy method returned false for lookup [%s]:[%s]",
            lookupName,
            container
        );
      }
    }
  }

  @VisibleForTesting
  interface Notice
  {
    void handle(Map<String, LookupExtractorFactoryContainer> lookupMap, LookupReferencesManager manager)
        throws Exception;
  }

  private static class LoadNotice implements Notice
  {
    private final String lookupName;

View on GitHub (pinned to 9b90983fd2)