apache/druid · error · IllegalStateException

Multiple joinable factories are valid for table[%s]

Error message

Multiple joinable factories are valid for table[%s]

What it means

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.

Source

Thrown at processing/src/main/java/org/apache/druid/segment/join/MapJoinableFactory.java:92

  @Override
  public Optional<byte[]> computeJoinCacheKey(DataSource dataSource, JoinConditionAnalysis condition)
  {
    return getSingleResult(dataSource, factory -> factory.computeJoinCacheKey(dataSource, condition));
  }

  /**
   * Computes the given function assuming that only one joinable factory will return a non-empty result. If we get
   * results from two {@link JoinableFactory}, then throw an exception.
   *
   */
  private <T> Optional<T> getSingleResult(DataSource dataSource, Function<JoinableFactory, Optional<T>> function)
  {
    Set<JoinableFactory> factories = joinableFactories.get(dataSource.getClass());
    Optional<T> mayBeFinalResult = Optional.empty();
    for (JoinableFactory joinableFactory : factories) {
      Optional<T> candidate = function.apply(joinableFactory);
      if (candidate.isPresent() && mayBeFinalResult.isPresent()) {
        throw new ISE("Multiple joinable factories are valid for table[%s]", dataSource);
      }
      if (candidate.isPresent()) {
        mayBeFinalResult = candidate;
      }
    }
    return mayBeFinalResult;
  }
}

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Check extension/module configuration and remove the duplicate JoinableFactory binding for that dataSource class
  2. Ensure custom JoinableFactory implementations do not overlap with built-in factories for the same dataSource type
  3. Log which factories claim the dataSource and disable or scope one of them

Example fix

// before (Guice multibinder adds two factories for TableDataSource)
binder.addBinding().toInstance(new MyJoinableFactory()); // conflicts with existing
// after
// remove or guard the duplicate binding so only one factory handles the type
Defensive patterns

Strategy: validation

Validate before calling

long claiming = factories.stream().filter(f -> f.canJoin(dataSource)).count();
if (claiming > 1) throw new IllegalStateException(claiming + " factories claim " + dataSource);

Try / catch

try { factory.build(...); } catch (IllegalStateException e) { log.error("multiple joinable factories registered for table", e); throw e; }

Prevention

When it happens

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

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

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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