apache/druid · error · IllegalArgumentException
integer values must be positive, got
Error message
integer values must be positive, got[%d], i[%d]
What it means
VSizeColumnarInts.writeToBuffer validates every value before packing it into a variable-width buffer; negative integers cannot be encoded, so any val < 0 throws this IAE naming the offending value and its index. It is a data-validation guard during column serialization (via fromIndexedInts).
Solutions
- Sanitize input data so values are non-negative before building the column (clamp or map sentinels to 0/null)
- Fix the upstream producer (aggregator/expression) that emits negative values
- If negatives are legitimate, use a column type that supports signed values instead of VSizeColumnarInts
Example fix
// before
VSizeColumnarInts.fromIndexedInts(rawInts, maxValue);
// after
for (int i = 0; i < rawInts.size(); i++) { if (rawInts.get(i) < 0) throw new IllegalArgumentException("negative at " + i); }
VSizeColumnarInts.fromIndexedInts(rawInts, maxValue); Defensive patterns
Strategy: validation
Validate before calling
for (int i = 0, size = ints.size(); i < size; i++) { if (ints.get(i) < 0) throw new IllegalArgumentException("negative value at index " + i); } Type guard
boolean isNonNegative(IndexedInts ints) { for (int i = 0; i < ints.size(); i++) { if (ints.get(i) < 0) return false; } return true; } Try / catch
try { col = VSizeColumnarInts.fromIndexedInts(ints, maxValue); } catch (IAE e) { if (e.getMessage().contains("must be positive")) { log.error("negative value in column data"); } throw e; } Prevention
- Sanitize numeric dimensions before ingestion so sentinels like -1 become null/0
- Unit-test aggregators for negative outputs
- Prefer signed-capable column types when negatives are legitimate
When it happens
Trigger: Calling VSizeColumnarInts.fromIndexedInts (or dimension serialization) with an IndexedInts containing a negative int, e.g. from a dictionary lookup returning -1 or a broken aggregator.
Common situations: Ingestion of numeric dimension values where a sentinel -1 or missing-value marker was not translated, custom aggregators producing negative dictionary ids, or integer underflow in upstream transforms.
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
- bucketSize must be a power of two but was[%,d]
- bucketSize must be a power of two but was[%,d]
- bucketSize must be a power of two (from 1 up to 128) but…
- maxValue[ ] must be positive
- val[ ] > maxValue[ ], please don't lie about maxValue. i[ ]
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/30f3f7d1bd783970.
Report an issue: GitHub.
Appendix: source
Thrown at processing/src/main/java/org/apache/druid/segment/data/VSizeColumnarInts.java:72
}
public static VSizeColumnarInts fromIndexedInts(IndexedInts ints, int maxValue)
{
int numBytes = getNumBytesForMax(maxValue);
final ByteBuffer buffer = ByteBuffer.allocate((ints.size() * numBytes) + (4 - numBytes));
writeToBuffer(buffer, ints, numBytes, maxValue);
return new VSizeColumnarInts(buffer.asReadOnlyBuffer(), numBytes);
}
private static void writeToBuffer(ByteBuffer buffer, IndexedInts ints, int numBytes, int maxValue)
{
ByteBuffer helperBuffer = ByteBuffer.allocate(Integer.BYTES);
for (int i = 0, size = ints.size(); i < size; i++) {
int val = ints.get(i);
if (val < 0) {
throw new IAE("integer values must be positive, got[%d], i[%d]", val, i);
}
if (val > maxValue) {
throw new IAE("val[%d] > maxValue[%d], please don't lie about maxValue. i[%d]", val, maxValue, i);
}
helperBuffer.putInt(0, val);
buffer.put(helperBuffer.array(), Integer.BYTES - numBytes, numBytes);
}
buffer.position(0);
}
public static byte getNumBytesForMax(int maxValue)
{
if (maxValue < 0) {
throw new IAE("maxValue[%s] must be positive", maxValue);
}
if (maxValue <= 0xFF) {View on GitHub (pinned to 9b90983fd2)