{"record":{"id":"d275e8eba1ead773","repo":"apache/druid","slug":"bad-null-marker-byte-delegate-class-s","errorCode":null,"errorMessage":"Bad null-marker byte, delegate class[%s]","messagePattern":"Bad null-marker byte, delegate class\\[(.+?)\\]","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"critical","filePath":"processing/src/main/java/org/apache/druid/query/aggregation/NullableNumericVectorAggregator.java","lineNumber":148,"sourceCode":"\n      doAggregate(buf, j, vAggregationPositions, vAggregationRows, positionOffset);\n    } else {\n      doAggregate(buf, numRows, positions, rows, positionOffset);\n    }\n  }\n\n  @Override\n  @Nullable\n  public Object get(ByteBuffer buf, int position)\n  {\n    switch (buf.get(position)) {\n      case TypeStrategies.IS_NULL_BYTE:\n        return null;\n      case TypeStrategies.IS_NOT_NULL_BYTE:\n        return delegate.get(buf, position + Byte.BYTES);\n      default:\n        // Corrupted byte?\n        throw new ISE(\"Bad null-marker byte, delegate class[%s]\", delegate.getClass().getName());\n    }\n  }\n\n  @Override\n  public void relocate(int oldPosition, int newPosition, ByteBuffer oldBuffer, ByteBuffer newBuffer)\n  {\n    delegate.relocate(oldPosition + Byte.BYTES, newPosition + Byte.BYTES, oldBuffer, newBuffer);\n  }\n\n  @Override\n  public void close()\n  {\n    delegate.close();\n  }\n\n  private void doAggregate(ByteBuffer buf, int position, int start, int end)\n  {\n    buf.put(position, TypeStrategies.IS_NOT_NULL_BYTE);","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/aggregation/NullableNumericVectorAggregator.java#L130-L166","documentation":"NullableNumericVectorAggregator.get() reads a one-byte null marker stored in the buffer in front of each aggregated value. Valid markers are IS_NULL_BYTE (return null) and IS_NOT_NULL_BYTE (delegate to the wrapped vector aggregator). Any other byte means the buffer contents are corrupted or misaligned, so an ISE including the delegate class name is thrown to expose the corruption immediately rather than decoding garbage as a number.","triggerScenarios":"A null-marker byte in the buffer holds neither TypeStrategies.IS_NULL_BYTE nor IS_NOT_NULL_BYTE — caused by buffer corruption, wrong position arithmetic (reading a value byte instead of the marker), uninitialized group slots, or a custom aggregator that doesn't follow the null-marker protocol.","commonSituations":"Custom TypeStrategies/vector aggregators that forget to write the marker byte on init; buffer relocation (relocate()) bugs that copy value bytes but not the flag byte; position off-by-one errors; mixed Druid versions or third-party aggregation extensions writing incompatible buffer layouts.","solutions":["Ensure every group-buffer slot is initialized via init()/putNull() so the marker byte is always written before get() is called","Audit relocate()/copy logic so the null-marker byte moves together with the value","Verify all delegate reads/writes use position + Byte.BYTES offsets consistently","Check for version mismatches between the aggregation extension and core Druid that would change the buffer layout"],"exampleFix":"// before\nreturn delegate.get(buf, position); // skips null marker entirely\n// after\nbyte marker = buf.get(position);\nif (marker == TypeStrategies.IS_NULL_BYTE) {\n  return null;\n}\nreturn delegate.get(buf, position + Byte.BYTES);","handlingStrategy":"validation","validationCode":"// defensive read of the marker before get()\nbyte marker = buf.get(position);\nif (marker != TypeStrategies.IS_NULL_BYTE && marker != TypeStrategies.IS_NOT_NULL_BYTE) {\n  throw new IllegalStateException(\"Corrupted null-marker byte: \" + marker);\n}","typeGuard":null,"tryCatchPattern":"try {\n  return aggregator.get(buf, position);\n} catch (ISE e) {\n  if (e.getMessage().startsWith(\"Bad null-marker byte\")) {\n    LOG.error(e, \"corrupted aggregation buffer at position %d\", position);\n    return null; // or fail the query explicitly\n  }\n  throw e;\n}","preventionTips":["Always initialize group-buffer slots (init/putNull) so the marker byte is never stale garbage","Ensure relocate() copies the null-marker byte along with the value","Use consistent position + Byte.BYTES offsets in all delegate calls","Pin compatible versions of Druid core and any custom aggregation extensions sharing the buffer layout"],"tags":["java","aggregation","bytebuffer","corruption","vectorized"],"backgroundTag":"internal-invariant-violation","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"}