{"record":{"id":"0d65c0a8edc79e58","repo":"apache/druid","slug":"maxvalue-s-must-be-positive-0d65c0","errorCode":null,"errorMessage":"maxValue[%s] must be positive","messagePattern":"maxValue\\[(.+?)\\] must be positive","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/segment/data/VSizeLongSerde.java","lineNumber":47,"sourceCode":"import java.io.OutputStream;\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\n\n/**\n * Currently only support big endian\n * <p>\n * An empty 4 bytes is written upon closing to avoid index out of bound exception for deserializers that shift bytes\n */\npublic class VSizeLongSerde\n{\n\n  public static final int[] SUPPORTED_SIZES = {1, 2, 4, 8, 12, 16, 20, 24, 32, 40, 48, 56, 64};\n  public static final byte[] EMPTY = {0, 0, 0, 0};\n\n  public static int getBitsForMax(long value)\n  {\n    if (value < 0) {\n      throw new IAE(\"maxValue[%s] must be positive\", value);\n    }\n    byte numBits = 0;\n    long maxValue = 1;\n    for (int supportedSize : SUPPORTED_SIZES) {\n      while (numBits < supportedSize && maxValue < Long.MAX_VALUE / 2) {\n        numBits++;\n        maxValue *= 2;\n      }\n      if (value <= maxValue || maxValue >= Long.MAX_VALUE / 2) {\n        return supportedSize;\n      }\n    }\n    return 64;\n  }\n\n  public static int getSerializedSize(int bitsPerValue, int numValues)\n  {\n    // this value is calculated by rounding up the byte and adding the 4 closing bytes","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/segment/data/VSizeLongSerde.java#L29-L65","documentation":"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.","triggerScenarios":"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.","commonSituations":"Building long dimension/metric columns where sentinel -1 leaked in, or swapping minValue for maxValue in the sizing call.","solutions":["Fix the max computation to initialize at 0 and use Math.max over the data","Clamp the argument: Math.max(0, maxValue)","If negative values are real data, choose a fixed-width signed encoding instead of VSizeLongSerde"],"exampleFix":"// before\nlong maxValue = Long.MIN_VALUE; for (long v : vals) maxValue = Math.min(maxValue, v);\nint bits = VSizeLongSerde.getBitsForMax(maxValue);\n// after\nlong maxValue = 0; for (long v : vals) maxValue = Math.max(maxValue, v);\nint bits = VSizeLongSerde.getBitsForMax(maxValue);","handlingStrategy":"validation","validationCode":"if (value < 0) throw new IllegalArgumentException(\"value must be non-negative, got \" + value);\nint bits = VSizeLongSerde.getBitsForMax(value);","typeGuard":"boolean isValidMaxValue(long v) { return v >= 0; }","tryCatchPattern":"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; }","preventionTips":["Initialize long max accumulators at 0, not Long.MIN_VALUE","Filter or map negative sentinels out before column sizing","Use signed fixed-width encodings when negative data is expected"],"tags":["validation","long","bounds"],"backgroundTag":"invalid-argument-value","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}