apache/druid · error · IllegalArgumentException

Cannot deserialize type[%s] to an RoaringBitmap64Counter:

Error message

Cannot deserialize type[%s] to an RoaringBitmap64Counter:

What it means

deserializeRoaringBitmap64Counter accepts only String (base64-encoded), byte[], and RoaringBitmap64Counter values; anything else (null included) triggers IllegalArgumentException("Cannot deserialize type[%s] to an RoaringBitmap64Counter:"). Note the message has a formatting bug: %s is present but no argument is substituted after the class name, so the message ends with a colon. It indicates the input value's Java type is not one of the supported representations of a RoaringBitmap64Counter.

Source

Thrown at extensions-contrib/druid-exact-count-bitmap/src/main/java/org/apache/druid/query/aggregation/exact/count/bitmap64/Bitmap64ExactCountMergeComplexMetricSerde.java:48

import org.apache.druid.segment.serde.ComplexMetricExtractor;
import org.apache.druid.segment.serde.ComplexMetricSerde;
import org.apache.druid.segment.serde.LargeColumnSupportedComplexColumnSerializer;
import org.apache.druid.segment.writeout.SegmentWriteOutMedium;

import java.nio.ByteBuffer;

public class Bitmap64ExactCountMergeComplexMetricSerde extends ComplexMetricSerde
{
  static RoaringBitmap64Counter deserializeRoaringBitmap64Counter(final Object object)
  {
    if (object instanceof String) {
      return RoaringBitmap64Counter.fromBytes(decodeStringToByteArray((String) object));
    } else if (object instanceof byte[]) {
      return RoaringBitmap64Counter.fromBytes((byte[]) object);
    } else if (object instanceof RoaringBitmap64Counter) {
      return (RoaringBitmap64Counter) object;
    }
    throw new IAE("Cannot deserialize type[%s] to an RoaringBitmap64Counter:", object.getClass().getName());
  }

  private static byte[] decodeStringToByteArray(String string)
  {
    try {
      return StringUtils.decodeBase64(StringUtils.toUtf8(string));
    }
    catch (IllegalArgumentException e) {
      throw new IAE("Failed to deserialize to RoaringBitmap64Counter, input is an invalid base64 string");
    }
  }

  @Override
  public String getTypeName()
  {
    return Bitmap64ExactCountModule.TYPE_NAME; // must be common type name
  }

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Inspect the actual Java class logged in the error and convert it before deserialization: pass either a base64 String, a byte[], or an already-built RoaringBitmap64Counter.
  2. If the value arrives as a ByteBuffer, copy it out: byte[] bytes = new byte[buf.remaining()]; buf.get(bytes);
  3. If the value is null, guard it in your extraction/parsing code and skip or default the metric rather than passing null to the serde.
  4. If a connector/parser is producing the wrong type, add an input transform (flattenJSON / deserialize step) to normalize the column to a base64 string.
  5. Report/patch the message formatting bug (the %s placeholder is never filled) if you maintain a fork.

Example fix

// before
Object v = row.getRaw(column);
RoaringBitmap64Counter c = serde.extractValue(v); // may be ByteBuffer
// after
Object v = row.getRaw(column);
if (v instanceof ByteBuffer) {
  ByteBuffer b = (ByteBuffer) v;
  byte[] bytes = new byte[b.remaining()];
  b.get(bytes);
  v = bytes;
}
RoaringBitmap64Counter c = serde.extractValue(v);
Defensive patterns

Strategy: type-guard

Validate before calling

if (value == null) return null; // or default counter
if (!(value instanceof String || value instanceof byte[] || value instanceof RoaringBitmap64Counter)) {
  throw new IllegalArgumentException("Unsupported bitmap value type: " + value.getClass());
}

Type guard

static boolean isDeserializableBitmap(Object o) {
  return o instanceof RoaringBitmap64Counter
      || o instanceof byte[]
      || o instanceof String;
}

Try / catch

try {
  RoaringBitmap64Counter c = serde.extractValue(rawValue);
} catch (IllegalArgumentException e) {
  // normalize the raw value (ByteBuffer -> byte[], null -> skip) and retry once
}

Prevention

When it happens

Trigger: Calling extractValue / deserializeRoaringBitmap64Counter with an object that is not a String, byte[], or RoaringBitmap64Counter — e.g. a ByteBuffer, List, Map, or null returned by the input layer for a BITMAP64_EXACT_COUNT column.

Common situations: Ingesting from a source (JSON/Avro/Parquet/Kafka) where the bitmap metric column arrives as a non-string structured type; a Druid version or parser change altering the deserialized Java type; query-time lookups handing back native objects; feeding raw serialized payloads through the wrong serde.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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