{"record":{"id":"d224a7d5d3e7c1db","repo":"apache/druid","slug":"datasources-must-be-non-null-and-non-empty-for","errorCode":null,"errorMessage":"'dataSources' must be non-null and non-empty for 'union'","messagePattern":"'dataSources' must be non-null and non-empty for 'union'","errorType":"validation","errorClass":"IllegalStateException","httpStatus":400,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/query/UnionDataSource.java","lineNumber":55,"sourceCode":" * <p>\n * Native engine can only work with table datasources that are scans or simple mappings (column rename without any\n * expression applied on top). Therefore, it uses methods like {@link #getTableNames()} and\n * {@link #isTableBased()} to assert that the children were TableDataSources.\n * <p>\n * MSQ should be able to plan and work with arbitrary datasources.  It also needs to replace the datasource with the\n * InputNumberDataSource while preparing the query plan.\n */\npublic class UnionDataSource implements DataSource\n{\n\n  @JsonProperty(\"dataSources\")\n  private final List<DataSource> dataSources;\n\n  @JsonCreator\n  public UnionDataSource(@JsonProperty(\"dataSources\") List<DataSource> dataSources)\n  {\n    if (dataSources == null || dataSources.isEmpty()) {\n      throw new ISE(\"'dataSources' must be non-null and non-empty for 'union'\");\n    }\n\n    this.dataSources = dataSources;\n  }\n\n  /**\n   * Asserts that the children of the union are all table data sources before returning the table names\n   */\n  @Override\n  public Set<String> getTableNames()\n  {\n    if (!isTableBased()) {\n      throw DruidException.defensive(\"contains non-table based datasource\");\n    }\n    return dataSources\n        .stream()\n        .map(DataSource::getTableNames)\n        .flatMap(Set::stream)","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/UnionDataSource.java#L37-L73","documentation":"UnionDataSource's @JsonCreator constructor requires a non-null, non-empty dataSources list; a union of zero sources is meaningless and Druid throws ISE during deserialization or direct construction. This is an eager constructor guard so invalid unions never enter the query tree.","triggerScenarios":"Deserializing JSON {\"type\":\"union\",\"dataSources\":[]} or null dataSources; programmatically calling new UnionDataSource(emptyList) after filtering out all sources from a union.","commonSituations":"Query-building code that collects sub-datasources into a list, all of which were filtered out, leaving an empty list; hand-written query JSON missing the dataSources array.","solutions":["Ensure the dataSources JSON array has at least one datasource","In query-building code, check the list is non-empty before constructing the union, else fall back to the single source or skip the union","Validate query JSON before submitting"],"exampleFix":"// before\nreturn new UnionDataSource(collectedSources); // collectedSources may be empty\n// after\nif (collectedSources.size() == 1) {\n  return collectedSources.get(0);\n} else if (collectedSources.isEmpty()) {\n  throw new IAE(\"No datasources to union\");\n}\nreturn new UnionDataSource(collectedSources);","handlingStrategy":"validation","validationCode":"if (dataSources == null || dataSources.isEmpty()) {\n  throw new IllegalArgumentException(\"Union requires at least one datasource\");\n}\nnew UnionDataSource(dataSources);","typeGuard":"boolean isUnionConstructible(List<DataSource> ds) {\n  return ds != null && !ds.isEmpty();\n}","tryCatchPattern":"try {\n  DataSource u = new UnionDataSource(dataSources);\n} catch (IllegalStateException e) {\n  // fall back to single datasource or reject query\n}","preventionTips":["Drop empty branches before constructing a union","Validate query JSON dataSources array before submission"],"tags":["druid","datasource","deserialization","empty-collection"],"backgroundTag":"empty-required-field","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"}