{"record":{"id":"927f529cde9080cd","repo":"apache/druid","slug":"extension-s-has-a-circular-druid-extension-depe","errorCode":null,"errorMessage":"Extension [%s] has a circular druid extension dependency. Dependency stack [%s].","messagePattern":"Extension \\[(.+?)\\] has a circular druid extension dependency\\. Dependency stack \\[(.+?)\\]\\.","errorType":"validation","errorClass":"RE","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/guice/ExtensionsLoader.java","lineNumber":293,"sourceCode":"\n    List<ClassLoader> extensionDependencyClassLoaders = new ArrayList<>();\n    for (String druidExtensionDependencyName : druidExtensionDependenciesList) {\n      Optional<File> extensionDependencyFileOptional = Arrays.stream(getExtensionFilesToLoad())\n          .filter(file -> file.getName().equals(druidExtensionDependencyName))\n          .findFirst();\n      if (!extensionDependencyFileOptional.isPresent()) {\n        throw new RE(\n            StringUtils.format(\n                \"Extension [%s] depends on [%s] which is not a valid extension or not loaded.\",\n                extension.getName(),\n                druidExtensionDependencyName\n            )\n        );\n      }\n      File extensionDependencyFile = extensionDependencyFileOptional.get();\n      if (extensionDependencyStack.contains(extensionDependencyFile.getName())) {\n        extensionDependencyStack.add(extensionDependencyFile.getName());\n        throw new RE(\n            StringUtils.format(\n                \"Extension [%s] has a circular druid extension dependency. Dependency stack [%s].\",\n                extensionDependencyStack.get(0),\n                extensionDependencyStack\n            )\n        );\n      }\n      extensionDependencyStack.add(extensionDependencyFile.getName());\n      extensionDependencyClassLoaders.add(\n          getClassLoaderForExtension(extensionDependencyFile, useExtensionClassloaderFirst, extensionDependencyStack)\n      );\n    }\n\n    return makeClassLoaderForExtension(extension, useExtensionClassloaderFirst, extensionDependencyClassLoaders);\n  }\n\n  private static StandardURLClassLoader makeClassLoaderForExtension(\n      final File extension,","sourceCodeStart":275,"sourceCodeEnd":311,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/guice/ExtensionsLoader.java#L275-L311","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Edit the druid-extension-dependencies.json of one of the extensions to remove the cycle","Restructure: move shared code into a common extension that both depend on, instead of depending on each other","Check the printed dependency stack to identify the exact loop and which declaration to drop","Downgrade/replace one extension with a version without the circular declaration"],"exampleFix":"// before (ext-B deps)\n{\"druid.extension.dependencies\":[\"ext-A\"]} // ext-A deps: [\"ext-B\"]\n// after\n{\"druid.extension.dependencies\":[]}","handlingStrategy":"validation","validationCode":"void checkAcyclic(Map<String,List<String>> deps) {\n  for (String start : deps.keySet()) {\n    Set<String> seen = new HashSet<>();\n    Deque<String> stack = new ArrayDeque<>(List.of(start));\n    while (!stack.isEmpty()) {\n      String cur = stack.pop();\n      if (!seen.add(cur)) throw new IllegalStateException(\"Cycle at \" + cur);\n      deps.getOrDefault(cur, List.of()).forEach(stack::push);\n    }\n  }\n}","typeGuard":null,"tryCatchPattern":"try { getClassLoaderForExtension(ext); } catch (RE e) {\n  if (e.getMessage().contains(\"circular druid extension dependency\")) { log.error(e.getMessage()); }\n  throw e;\n}","preventionTips":["Draw the extension dependency graph before adding new declarations","Never declare A depends-on B and B depends-on A; factor shared code out","Review druid-extension-dependencies.json changes in code review"],"tags":["extensions","dependency","circular-dependency"],"backgroundTag":"circular-dependency","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"}