{"record":{"id":"05d6ff67fdabb951","repo":"apache/druid","slug":"cannot-add-to-a-spectator-histogram","errorCode":null,"errorMessage":"Cannot add / to a Spectator Histogram","messagePattern":"Cannot add / to a Spectator Histogram","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"extensions-contrib/spectator-histogram/src/main/java/org/apache/druid/spectator/histogram/SpectatorHistogram.java","lineNumber":321,"sourceCode":"  {\n    if (bucket >= PercentileBuckets.length() || bucket < 0) {\n      throw new IAE(\"Bucket index out of range (0, \" + PercentileBuckets.length() + \")\");\n    }\n    writableMap().addTo((short) bucket, count);\n    this.sumOfCounts += count;\n  }\n\n  private void add(Object key, Number value)\n  {\n    if (key instanceof String) {\n      this.add(Integer.parseInt((String) key), value.longValue());\n      return;\n    }\n    if (Number.class.isAssignableFrom(key.getClass())) {\n      this.add(((Number) key).intValue(), value.longValue());\n      return;\n    }\n    throw new IAE(\n        \"Cannot add \" + key.getClass() + \"/\" + value.getClass() + \" to a Spectator Histogram\"\n    );\n  }\n\n  // Used for testing\n  long get(int idx)\n  {\n    return readableMap().get((short) idx);\n  }\n\n  // Accessible for serialization\n  void serialize(JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException\n  {\n    JacksonUtils.writeObjectUsingSerializerProvider(jsonGenerator, serializerProvider, readableMap());\n  }\n\n  public boolean isEmpty()\n  {","sourceCodeStart":303,"sourceCodeEnd":339,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/extensions-contrib/spectator-histogram/src/main/java/org/apache/druid/spectator/histogram/SpectatorHistogram.java#L303-L339","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure the map passed to deserialize has numeric keys (produce it via SpectatorHistogram.serialize).","Convert String keys to integers/bucket indices before deserialization.","If ingesting JSON, use a serializer that preserves integer keys rather than coercing them to strings."],"exampleFix":"// before\nMap<String, Long> m = readJson();\nSpectatorHistogram h = SpectatorHistogram.deserialize(m);\n// after\nMap<Integer, Long> m = readJson(); // integer keys preserved\nSpectatorHistogram h = SpectatorHistogram.deserialize(m);","handlingStrategy":"type-guard","validationCode":"static boolean isDeserializableEntry(Map.Entry<?, ?> e) {\n  return e.getKey() instanceof Number || e.getKey() instanceof byte[];\n}","typeGuard":"static boolean canAddKey(Object key) {\n  return key instanceof Number || key instanceof byte[];\n}","tryCatchPattern":"try {\n  SpectatorHistogram h = SpectatorHistogram.deserialize(map);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().contains(\"Cannot add\")) {\n    throw new IAE(\"Serialized map has non-numeric keys; regenerate with SpectatorHistogram.serialize\", e);\n  }\n  throw e;\n}","preventionTips":["Ensure serialization preserves numeric map key types (avoid generic JSON mappers that stringify keys).","Guard each map key with instanceof Number before deserializing.","Round-trip a sample histogram in tests to catch serde drift."],"tags":["illegal-argument","type-mismatch","deserialization"],"backgroundTag":"type-mismatch","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}