{"record":{"id":"0e1cf2273b4dc286","repo":"apache/druid","slug":"bf1length-d-does-not-match-bf2length-d","errorCode":null,"errorMessage":"bf1Length %d does not match bf2Length %d","messagePattern":"bf1Length (.+?) does not match bf2Length (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"extensions-core/druid-bloom-filter/src/main/java/org/apache/druid/query/filter/BloomKFilter.java","lineNumber":206,"sourceCode":"   *\n   * @param bf1Bytes\n   * @param bf1Start\n   * @param bf1Length\n   * @param bf2Bytes\n   * @param bf2Start\n   * @param bf2Length\n   */\n  public static void mergeBloomFilterBytes(\n      byte[] bf1Bytes,\n      int bf1Start,\n      int bf1Length,\n      byte[] bf2Bytes,\n      int bf2Start,\n      int bf2Length\n  )\n  {\n    if (bf1Length != bf2Length) {\n      throw new IllegalArgumentException(\"bf1Length \" + bf1Length + \" does not match bf2Length \" + bf2Length);\n    }\n\n    // Validation on the bitset size/3 hash functions.\n    for (int idx = 0; idx < START_OF_SERIALIZED_LONGS; ++idx) {\n      if (bf1Bytes[bf1Start + idx] != bf2Bytes[bf2Start + idx]) {\n        throw new IllegalArgumentException(\"bf1 NumHashFunctions/NumBits does not match bf2\");\n      }\n    }\n\n    // Just bitwise-OR the bits together - size/# functions should be the same,\n    // rest of the data is serialized long values for the bitset which are supposed to be bitwise-ORed.\n    for (int idx = START_OF_SERIALIZED_LONGS; idx < bf1Length; ++idx) {\n      bf1Bytes[bf1Start + idx] |= bf2Bytes[bf2Start + idx];\n    }\n  }\n\n  public static void serialize(ByteBuffer out, BloomKFilter bloomFilter)\n  {","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/extensions-core/druid-bloom-filter/src/main/java/org/apache/druid/query/filter/BloomKFilter.java#L188-L224","documentation":"BloomKFilter.mergeBloomFilterBytes() merges two serialized bloom filter byte arrays by OR-ing their bitsets, which is only valid when both filters share identical serialization parameters (num hash functions, bitset size) and therefore identical byte lengths. When bf1Length != bf2Length it throws IllegalArgumentException reporting both lengths; a following header check also rejects mismatched hash/bit parameters.","triggerScenarios":"Merging bloom filters created with different maxNumEntries/numBits or different numHashFunctions, e.g. combining query results from segments or subqueries configured with different maxNumEntries; passing truncated or corrupted serialized buffers; merging a partially-read buffer whose length slice is wrong.","commonSituations":"Aggregating bloom filters across subqueries where one used default maxNumEntries (1500) and another a custom value; hand-slicing serialized byte arrays with incorrect start/length offsets; upgrading configs so old stored filters no longer match new query filters.","solutions":["Use the same maxNumEntries (and numHashFunctions) for every bloom filter aggregation being merged, across all queries and subqueries","Verify byte offsets: pass the full serialized length for each buffer and correct start positions","Re-generate mismatched stored/serialized filters with the current parameters","Catch IllegalArgumentException and treat as incompatible filters if merging user-supplied filters of unknown provenance"],"exampleFix":"// before\nBloomKFilter f1 = new BloomKFilter(1000);\nBloomKFilter f2 = new BloomKFilter(1500); // different bitset size\nBloomKFilter.mergeBloomFilterBytes(f1.toByteBuffer().array(), 0, len1, f2.toByteBuffer().array(), 0, len2); // throws\n// after\nint maxNumEntries = 1500; // shared constant for all filters\nBloomKFilter f1 = new BloomKFilter(maxNumEntries);\nBloomKFilter f2 = new BloomKFilter(maxNumEntries);\nBloomKFilter.mergeBloomFilterBytes(f1.toByteBuffer().array(), 0, len1, f2.toByteBuffer().array(), 0, len2);","handlingStrategy":"validation","validationCode":"// Validate compatibility before merging\nstatic boolean mergeable(byte[] f1, int l1, byte[] f2, int l2) {\n  return f1 != null && f2 != null && l1 == l2;\n}","typeGuard":null,"tryCatchPattern":"try {\n  BloomKFilter.mergeBloomFilterBytes(bf1, s1, l1, bf2, s2, l2);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().contains(\"does not match\") || e.getMessage().contains(\"does not match bf2\")) {\n    throw new IllegalStateException(\"Incompatible bloom filter parameters (maxNumEntries/hash functions)\", e);\n  }\n  throw e;\n}","preventionTips":["Standardize maxNumEntries (and hash functions) across all queries/subqueries producing merged filters","Slice serialized buffers with correct start/length offsets","Regenerate stored filters after changing bloom filter config parameters"],"tags":["druid","bloom-filter","merge","length-mismatch","invalid-argument"],"backgroundTag":"invalid-argument-value","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"}