apache/druid · critical · IllegalStateException

Bad null-marker byte, delegate class[%s]

Error message

Bad null-marker byte, delegate class[%s]

What it means

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.

Source

Thrown at processing/src/main/java/org/apache/druid/query/aggregation/NullableNumericVectorAggregator.java:148

      doAggregate(buf, j, vAggregationPositions, vAggregationRows, positionOffset);
    } else {
      doAggregate(buf, numRows, positions, rows, positionOffset);
    }
  }

  @Override
  @Nullable
  public Object get(ByteBuffer buf, int position)
  {
    switch (buf.get(position)) {
      case TypeStrategies.IS_NULL_BYTE:
        return null;
      case TypeStrategies.IS_NOT_NULL_BYTE:
        return delegate.get(buf, position + Byte.BYTES);
      default:
        // Corrupted byte?
        throw new ISE("Bad null-marker byte, delegate class[%s]", delegate.getClass().getName());
    }
  }

  @Override
  public void relocate(int oldPosition, int newPosition, ByteBuffer oldBuffer, ByteBuffer newBuffer)
  {
    delegate.relocate(oldPosition + Byte.BYTES, newPosition + Byte.BYTES, oldBuffer, newBuffer);
  }

  @Override
  public void close()
  {
    delegate.close();
  }

  private void doAggregate(ByteBuffer buf, int position, int start, int end)
  {
    buf.put(position, TypeStrategies.IS_NOT_NULL_BYTE);

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Ensure every group-buffer slot is initialized via init()/putNull() so the marker byte is always written before get() is called
  2. Audit relocate()/copy logic so the null-marker byte moves together with the value
  3. Verify all delegate reads/writes use position + Byte.BYTES offsets consistently
  4. Check for version mismatches between the aggregation extension and core Druid that would change the buffer layout

Example fix

// before
return delegate.get(buf, position); // skips null marker entirely
// after
byte marker = buf.get(position);
if (marker == TypeStrategies.IS_NULL_BYTE) {
  return null;
}
return delegate.get(buf, position + Byte.BYTES);
Defensive patterns

Strategy: validation

Validate before calling

// defensive read of the marker before get()
byte marker = buf.get(position);
if (marker != TypeStrategies.IS_NULL_BYTE && marker != TypeStrategies.IS_NOT_NULL_BYTE) {
  throw new IllegalStateException("Corrupted null-marker byte: " + marker);
}

Try / catch

try {
  return aggregator.get(buf, position);
} catch (ISE e) {
  if (e.getMessage().startsWith("Bad null-marker byte")) {
    LOG.error(e, "corrupted aggregation buffer at position %d", position);
    return null; // or fail the query explicitly
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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