{"record":{"id":"bb0e608ceb12fe5e","repo":"apache/druid","slug":"detected-conflicting-prefixes-in-join-clauses-s","errorCode":null,"errorMessage":"Detected conflicting prefixes in join clauses: [%s, %s]","messagePattern":"Detected conflicting prefixes in join clauses: \\[(.+?), (.+?)\\]","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/segment/join/JoinPrefixUtils.java","lineNumber":96,"sourceCode":"   *\n   * @param prefixes A mutable list containing the prefixes to check. This list will be sorted by descending\n   *                 string length.\n   */\n  public static void checkPrefixesForDuplicatesAndShadowing(\n      final List<String> prefixes\n  )\n  {\n    // this is a naive approach that assumes we'll typically handle only a small number of prefixes\n    prefixes.sort(DESCENDING_LENGTH_STRING_COMPARATOR);\n    for (int i = 0; i < prefixes.size(); i++) {\n      String prefix = prefixes.get(i);\n      for (int k = i + 1; k < prefixes.size(); k++) {\n        String otherPrefix = prefixes.get(k);\n        if (prefix.equals(otherPrefix)) {\n          throw new IAE(\"Detected duplicate prefix in join clauses: [%s]\", prefix);\n        }\n        if (isPrefixedBy(prefix, otherPrefix)) {\n          throw new IAE(\"Detected conflicting prefixes in join clauses: [%s, %s]\", prefix, otherPrefix);\n        }\n      }\n    }\n  }\n}\n","sourceCodeStart":78,"sourceCodeEnd":102,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/segment/join/JoinPrefixUtils.java#L78-L102","documentation":"The same validator also rejects prefix shadowing: one prefix that is a strict prefix-of another (e.g. 'l1.' and 'l1.x.'). Since a longer prefix's columns would also match the shorter one, column references become ambiguous and Druid throws this IllegalArgumentException.","triggerScenarios":"checkPrefixesForDuplicatesAndShadowing receives prefixes where one startsWith the other but they are not equal, e.g. ['l1.', 'l1.x.'] sorted descending, so isPrefixedBy(prefix, otherPrefix) is true.","commonSituations":"Nested/stacked joins where a sub-join's prefix accidentally begins with a parent join's prefix; manually chosen prefixes with overlapping namespaces.","solutions":["Change one prefix so no prefix is a prefix of another (use non-overlapping namespaces like l1., l2.)","Use distinct, non-nested naming for every joinable table in the query","If generated, update the prefix allocator to guarantee no shadowing"],"exampleFix":"// before\nprefixes: [\"l1.\", \"l1.x.\"]\n// after\nprefixes: [\"l1.\", \"l2.\"]","handlingStrategy":"validation","validationCode":"for (int i = 0; i < prefixes.size(); i++)\n  for (int k = i + 1; k < prefixes.size(); k++)\n    if (prefixes.get(i).startsWith(prefixes.get(k)) || prefixes.get(k).startsWith(prefixes.get(i)))\n      throw new IllegalArgumentException(\"shadowing prefixes\");","typeGuard":null,"tryCatchPattern":"try { checkPrefixes(...); } catch (IAE e) { /* pick new, non-overlapping prefixes and retry planning */ }","preventionTips":["Use fixed-width, non-nested prefix namespaces (l1., l2., ...)","Never derive a child join prefix from a parent prefix string","Validate prefixes in tests for every stacked-join query shape"],"tags":["java","join","druid","validation"],"backgroundTag":"conflicting-config-options","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"}