apache/druid · error · RE

Extension [ ] has a circular druid extension dependency…

Error message

Extension [%s] has a circular druid extension dependency. Dependency stack [%s].

What it means

While building classloaders for extension dependencies, ExtensionsLoader tracks the dependency stack and throws this RE when an extension's dependency chain loops back onto an extension already in the stack. Circular druid extension dependencies cannot be classloaded.

Solutions

  1. Edit the druid-extension-dependencies.json of one of the extensions to remove the cycle
  2. Restructure: move shared code into a common extension that both depend on, instead of depending on each other
  3. Check the printed dependency stack to identify the exact loop and which declaration to drop
  4. Downgrade/replace one extension with a version without the circular declaration

Example fix

// before (ext-B deps)
{"druid.extension.dependencies":["ext-A"]} // ext-A deps: ["ext-B"]
// after
{"druid.extension.dependencies":[]}
Defensive patterns

Strategy: validation

Validate before calling

void checkAcyclic(Map<String,List<String>> deps) {
  for (String start : deps.keySet()) {
    Set<String> seen = new HashSet<>();
    Deque<String> stack = new ArrayDeque<>(List.of(start));
    while (!stack.isEmpty()) {
      String cur = stack.pop();
      if (!seen.add(cur)) throw new IllegalStateException("Cycle at " + cur);
      deps.getOrDefault(cur, List.of()).forEach(stack::push);
    }
  }
}

Try / catch

try { getClassLoaderForExtension(ext); } catch (RE e) {
  if (e.getMessage().contains("circular druid extension dependency")) { log.error(e.getMessage()); }
  throw e;
}

Prevention

When it happens

Trigger: Extension A depends on B and B depends (directly or transitively) on A; when getClassLoaderForExtension resolves the chain, the dependency name reappears in the current stack.

Common situations: Hand-edited druid-extension-dependencies.json files creating a mutual dependency; refactoring extensions into multiple modules and leaving stale dependency entries; third-party extensions from different vendors each claiming the other as a dependency.

Related errors


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

Appendix: source

Thrown at processing/src/main/java/org/apache/druid/guice/ExtensionsLoader.java:293

    List<ClassLoader> extensionDependencyClassLoaders = new ArrayList<>();
    for (String druidExtensionDependencyName : druidExtensionDependenciesList) {
      Optional<File> extensionDependencyFileOptional = Arrays.stream(getExtensionFilesToLoad())
          .filter(file -> file.getName().equals(druidExtensionDependencyName))
          .findFirst();
      if (!extensionDependencyFileOptional.isPresent()) {
        throw new RE(
            StringUtils.format(
                "Extension [%s] depends on [%s] which is not a valid extension or not loaded.",
                extension.getName(),
                druidExtensionDependencyName
            )
        );
      }
      File extensionDependencyFile = extensionDependencyFileOptional.get();
      if (extensionDependencyStack.contains(extensionDependencyFile.getName())) {
        extensionDependencyStack.add(extensionDependencyFile.getName());
        throw new RE(
            StringUtils.format(
                "Extension [%s] has a circular druid extension dependency. Dependency stack [%s].",
                extensionDependencyStack.get(0),
                extensionDependencyStack
            )
        );
      }
      extensionDependencyStack.add(extensionDependencyFile.getName());
      extensionDependencyClassLoaders.add(
          getClassLoaderForExtension(extensionDependencyFile, useExtensionClassloaderFirst, extensionDependencyStack)
      );
    }

    return makeClassLoaderForExtension(extension, useExtensionClassloaderFirst, extensionDependencyClassLoaders);
  }

  private static StandardURLClassLoader makeClassLoaderForExtension(
      final File extension,

View on GitHub (pinned to 9b90983fd2)