{"record":{"id":"49c95443eb9391c2","repo":"apache/druid","slug":"multiple-joinable-factories-are-valid-for-table-s","errorCode":null,"errorMessage":"Multiple joinable factories are valid for table[%s]","messagePattern":"Multiple joinable factories are valid for table\\[(.+?)\\]","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/segment/join/MapJoinableFactory.java","lineNumber":92,"sourceCode":"  @Override\n  public Optional<byte[]> computeJoinCacheKey(DataSource dataSource, JoinConditionAnalysis condition)\n  {\n    return getSingleResult(dataSource, factory -> factory.computeJoinCacheKey(dataSource, condition));\n  }\n\n  /**\n   * Computes the given function assuming that only one joinable factory will return a non-empty result. If we get\n   * results from two {@link JoinableFactory}, then throw an exception.\n   *\n   */\n  private <T> Optional<T> getSingleResult(DataSource dataSource, Function<JoinableFactory, Optional<T>> function)\n  {\n    Set<JoinableFactory> factories = joinableFactories.get(dataSource.getClass());\n    Optional<T> mayBeFinalResult = Optional.empty();\n    for (JoinableFactory joinableFactory : factories) {\n      Optional<T> candidate = function.apply(joinableFactory);\n      if (candidate.isPresent() && mayBeFinalResult.isPresent()) {\n        throw new ISE(\"Multiple joinable factories are valid for table[%s]\", dataSource);\n      }\n      if (candidate.isPresent()) {\n        mayBeFinalResult = candidate;\n      }\n    }\n    return mayBeFinalResult;\n  }\n}\n","sourceCodeStart":74,"sourceCodeEnd":101,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/segment/join/MapJoinableFactory.java#L74-L101","documentation":"MapJoinableFactory.getSingleResult expects exactly one registered JoinableFactory to be able to handle a given dataSource. If more than one factory yields a non-empty result (e.g. two factories both claim they can build a joinable for the table), Druid throws this IllegalStateException because it cannot choose between them.","triggerScenarios":"Calling build() or computeJoinCacheKey() for a dataSource whose class has multiple registered JoinableFactory implementations that both return a present Optional, e.g. duplicate or custom extension factories registered for the same dataSource type.","commonSituations":"Installing two extensions that both register a JoinableFactory for the same table type (e.g. a custom factory conflicting with the lookup or broadcast factory); misconfigured Guice multibindings adding the same factory twice.","solutions":["Check extension/module configuration and remove the duplicate JoinableFactory binding for that dataSource class","Ensure custom JoinableFactory implementations do not overlap with built-in factories for the same dataSource type","Log which factories claim the dataSource and disable or scope one of them"],"exampleFix":"// before (Guice multibinder adds two factories for TableDataSource)\nbinder.addBinding().toInstance(new MyJoinableFactory()); // conflicts with existing\n// after\n// remove or guard the duplicate binding so only one factory handles the type","handlingStrategy":"validation","validationCode":"long claiming = factories.stream().filter(f -> f.canJoin(dataSource)).count();\nif (claiming > 1) throw new IllegalStateException(claiming + \" factories claim \" + dataSource);","typeGuard":null,"tryCatchPattern":"try { factory.build(...); } catch (IllegalStateException e) { log.error(\"multiple joinable factories registered for table\", e); throw e; }","preventionTips":["Audit Guice multibindings for JoinableFactory duplicates at startup","Ensure each extension's factory handles a disjoint set of dataSource types","Document which dataSource classes each custom factory supports"],"tags":["java","join","druid","dependency-injection"],"backgroundTag":"internal-invariant-violation","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}