apache/druid · error · IllegalArgumentException

Cannot add / to a Spectator Histogram

Error message

Cannot add / to a Spectator Histogram

What it means

The private add(Object key, Number value) overload accepts either a Number (treated as a bucket index) or a byte[] (lookup key); anything else is unsupported. When the key is neither, the library throws IAE because it cannot interpret the entry as a histogram bucket. This typically fires during deserialization of a map with unexpected key types.

Source

Thrown at extensions-contrib/spectator-histogram/src/main/java/org/apache/druid/spectator/histogram/SpectatorHistogram.java:321

  {
    if (bucket >= PercentileBuckets.length() || bucket < 0) {
      throw new IAE("Bucket index out of range (0, " + PercentileBuckets.length() + ")");
    }
    writableMap().addTo((short) bucket, count);
    this.sumOfCounts += count;
  }

  private void add(Object key, Number value)
  {
    if (key instanceof String) {
      this.add(Integer.parseInt((String) key), value.longValue());
      return;
    }
    if (Number.class.isAssignableFrom(key.getClass())) {
      this.add(((Number) key).intValue(), value.longValue());
      return;
    }
    throw new IAE(
        "Cannot add " + key.getClass() + "/" + value.getClass() + " to a Spectator Histogram"
    );
  }

  // Used for testing
  long get(int idx)
  {
    return readableMap().get((short) idx);
  }

  // Accessible for serialization
  void serialize(JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException
  {
    JacksonUtils.writeObjectUsingSerializerProvider(jsonGenerator, serializerProvider, readableMap());
  }

  public boolean isEmpty()
  {

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Ensure the map passed to deserialize has numeric keys (produce it via SpectatorHistogram.serialize).
  2. Convert String keys to integers/bucket indices before deserialization.
  3. If ingesting JSON, use a serializer that preserves integer keys rather than coercing them to strings.

Example fix

// before
Map<String, Long> m = readJson();
SpectatorHistogram h = SpectatorHistogram.deserialize(m);
// after
Map<Integer, Long> m = readJson(); // integer keys preserved
SpectatorHistogram h = SpectatorHistogram.deserialize(m);
Defensive patterns

Strategy: type-guard

Validate before calling

static boolean isDeserializableEntry(Map.Entry<?, ?> e) {
  return e.getKey() instanceof Number || e.getKey() instanceof byte[];
}

Type guard

static boolean canAddKey(Object key) {
  return key instanceof Number || key instanceof byte[];
}

Try / catch

try {
  SpectatorHistogram h = SpectatorHistogram.deserialize(map);
} catch (IllegalArgumentException e) {
  if (e.getMessage().contains("Cannot add")) {
    throw new IAE("Serialized map has non-numeric keys; regenerate with SpectatorHistogram.serialize", e);
  }
  throw e;
}

Prevention

When it happens

Trigger: deserialize() iterating a HashMap whose keys are e.g. Strings, Integers-as-Strings, or other objects rather than Number/byte[] entries; passing a map from a foreign serializer into SpectatorHistogram.deserialize.

Common situations: JSON round-tripping turns integer map keys into Strings, so deserializing JSON that was not produced by SpectatorHistogram's own serde hits this; custom aggregation plugins feed wrongly typed maps.

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/05d6ff67fdabb951. Report an issue: GitHub.