apache/druid · error · IllegalArgumentException

maxValue[ ] must be positive

Error message

maxValue[%s] must be positive

What it means

VSizeLongSerde.getBitsForMax computes the bit width needed to encode values up to the given long; negative values are unsupported, so a negative argument throws IAE 'maxValue must be positive'. Callers must supply a non-negative upper bound.

Solutions

  1. Fix the max computation to initialize at 0 and use Math.max over the data
  2. Clamp the argument: Math.max(0, maxValue)
  3. If negative values are real data, choose a fixed-width signed encoding instead of VSizeLongSerde

Example fix

// before
long maxValue = Long.MIN_VALUE; for (long v : vals) maxValue = Math.min(maxValue, v);
int bits = VSizeLongSerde.getBitsForMax(maxValue);
// after
long maxValue = 0; for (long v : vals) maxValue = Math.max(maxValue, v);
int bits = VSizeLongSerde.getBitsForMax(maxValue);
Defensive patterns

Strategy: validation

Validate before calling

if (value < 0) throw new IllegalArgumentException("value must be non-negative, got " + value);
int bits = VSizeLongSerde.getBitsForMax(value);

Type guard

boolean isValidMaxValue(long v) { return v >= 0; }

Try / catch

try { bits = VSizeLongSerde.getBitsForMax(maxValue); } catch (IAE e) { if (e.getMessage().contains("must be positive")) { maxValue = Math.max(0, maxValue); bits = VSizeLongSerde.getBitsForMax(maxValue); } else throw e; }

Prevention

When it happens

Trigger: Passing a negative long to getBitsForMax, usually from computing a max over data containing negatives or from an accumulator initialized to Long.MIN_VALUE / an erroneous subtraction.

Common situations: Building long dimension/metric columns where sentinel -1 leaked in, or swapping minValue for maxValue in the sizing call.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at processing/src/main/java/org/apache/druid/segment/data/VSizeLongSerde.java:47

import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * Currently only support big endian
 * <p>
 * An empty 4 bytes is written upon closing to avoid index out of bound exception for deserializers that shift bytes
 */
public class VSizeLongSerde
{

  public static final int[] SUPPORTED_SIZES = {1, 2, 4, 8, 12, 16, 20, 24, 32, 40, 48, 56, 64};
  public static final byte[] EMPTY = {0, 0, 0, 0};

  public static int getBitsForMax(long value)
  {
    if (value < 0) {
      throw new IAE("maxValue[%s] must be positive", value);
    }
    byte numBits = 0;
    long maxValue = 1;
    for (int supportedSize : SUPPORTED_SIZES) {
      while (numBits < supportedSize && maxValue < Long.MAX_VALUE / 2) {
        numBits++;
        maxValue *= 2;
      }
      if (value <= maxValue || maxValue >= Long.MAX_VALUE / 2) {
        return supportedSize;
      }
    }
    return 64;
  }

  public static int getSerializedSize(int bitsPerValue, int numValues)
  {
    // this value is calculated by rounding up the byte and adding the 4 closing bytes

View on GitHub (pinned to 9b90983fd2)