apache/druid · error · RE

Unable to compare unexpected types

Error message

Unable to compare unexpected types [%s]

What it means

The static COMPARATOR in BloomFilterAggregatorFactory can only compare pairs of ByteBuffer or pairs of BloomKFilter objects, ranking them by number of set bits. If it is handed any other type (or a mixed pair), it throws this RuntimeException with the actual class name of o1. This guards the query machinery against feeding unsupported intermediate objects into sorting/merging of bloom filter aggregation results.

Solutions

  1. Pass the object through the aggregator factory's deserialize(Object) method (base64 String -> ByteBuffer) before comparing
  2. Ensure only ByteBuffer (post-deserialize) or BloomKFilter objects are given to this comparator; convert byte[] with ByteBuffer.wrap
  3. Check all Druid cluster nodes run the same druid-bloom-filter extension version
  4. If using the comparator directly, instanceof-check inputs first and reject/convert unknown types

Example fix

// before
Object a = "<base64 string from JSON result>";
int cmp = factory.getComparator().compare(a, b); // throws RE
// after
Object a = factory.deserialize("<base64 string from JSON result>"); // -> ByteBuffer
int cmp = factory.getComparator().compare(a, factory.deserialize(b));
Defensive patterns

Strategy: type-guard

Validate before calling

// Java: ensure inputs are comparable before invoking COMPARATOR
static boolean comparable(Object o1, Object o2) {
  boolean b1 = o1 instanceof ByteBuffer || o1 instanceof BloomKFilter;
  boolean b2 = o2 instanceof ByteBuffer || o2 instanceof BloomKFilter;
  return b1 && b2 && o1.getClass().equals(o2.getClass());
}

Type guard

if (!(o instanceof ByteBuffer) && !(o instanceof BloomKFilter)) { o = factory.deserialize(o); }

Try / catch

try {
  comparator.compare(a, b);
} catch (RuntimeException e) {
  if (e.getMessage().startsWith("Unable to compare unexpected types")) {
    a = factory.deserialize(a); b = factory.deserialize(b); // retry after conversion
  } else throw e;
}

Prevention

When it happens

Trigger: Calling getComparator()-based comparisons (e.g. in merge/sort buffers or GroupByMerge query machinery) with objects that are neither ByteBuffer nor BloomKFilter, or comparing one of each; typically caused by a corrupted intermediate result, a custom aggregator subclass returning the wrong type, or deserialized results not passing through BloomFilterAggregatorFactory.deserialize().

Common situations: Distributed/group-by queries where intermediate results were serialized differently than expected; custom query tooling invoking the comparator directly with byte[] or String results (the native JSON result is base64 String, which deserialize() must convert to ByteBuffer first); version skew between nodes producing different intermediate representations.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

Thrown at extensions-core/druid-bloom-filter/src/main/java/org/apache/druid/query/aggregation/bloom/BloomFilterAggregatorFactory.java:71

  private static final int DEFAULT_NUM_ENTRIES = 1500;

  public static final Comparator COMPARATOR = Comparator.nullsFirst((o1, o2) -> {
    if (o1 instanceof ByteBuffer && o2 instanceof ByteBuffer) {
      ByteBuffer buf1 = (ByteBuffer) o1;
      ByteBuffer buf2 = (ByteBuffer) o2;
      return Integer.compare(
          BloomKFilter.getNumSetBits(buf1, buf1.position()),
          BloomKFilter.getNumSetBits(buf2, buf2.position())
      );
    } else if (o1 instanceof BloomKFilter && o2 instanceof BloomKFilter) {
      BloomKFilter f1 = (BloomKFilter) o1;
      BloomKFilter f2 = (BloomKFilter) o2;
      return Integer.compare(
          f1.getNumSetBits(),
          f2.getNumSetBits()
      );
    } else {
      throw new RE("Unable to compare unexpected types [%s]", o1.getClass().getName());
    }
  });

  private final String name;
  private final DimensionSpec field;
  private final int maxNumEntries;

  @JsonCreator
  public BloomFilterAggregatorFactory(
      @JsonProperty("name") String name,
      @JsonProperty("field") final DimensionSpec field,
      @JsonProperty("maxNumEntries") @Nullable Integer maxNumEntries
  )
  {
    this.name = name;
    this.field = field;
    this.maxNumEntries = maxNumEntries != null ? maxNumEntries : DEFAULT_NUM_ENTRIES;
  }

View on GitHub (pinned to 9b90983fd2)