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
- 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
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
- 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
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
- Column[%s] does not start with prefix[%s]
- Detected duplicate prefix in join clauses: [%s]
- Detected conflicting prefixes in join clauses: [%s, %s]
- Joining against a multi-value dimension is not supported.
- Joining against a ARRAY columns is not supported.
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/49c95443eb9391c2.
Report an issue: GitHub.