{"record":{"id":"8809f23b3af0bb97","repo":"apache/cassandra","slug":"invalid-decimal-value-expecting-at-least-4-bytes","errorCode":null,"errorMessage":"Invalid decimal value, expecting at least 4 bytes but got ","messagePattern":"Invalid decimal value, expecting at least 4 bytes but got ","errorType":"validation","errorClass":"InvalidTypeException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/cql3/functions/types/TypeCodec.java","lineNumber":1342,"sourceCode":"        {\n            if (value == null) return null;\n            BigInteger bi = value.unscaledValue();\n            int scale = value.scale();\n            byte[] bibytes = bi.toByteArray();\n\n            ByteBuffer bytes = ByteBuffer.allocate(4 + bibytes.length);\n            bytes.putInt(scale);\n            bytes.put(bibytes);\n            bytes.rewind();\n            return bytes;\n        }\n\n        @Override\n        public BigDecimal deserialize(ByteBuffer bytes, ProtocolVersion protocolVersion)\n        {\n            if (bytes == null || bytes.remaining() == 0) return null;\n            if (bytes.remaining() < 4)\n                throw new InvalidTypeException(\n                \"Invalid decimal value, expecting at least 4 bytes but got \" + bytes.remaining());\n\n            bytes = bytes.duplicate();\n            int scale = bytes.getInt();\n            byte[] bibytes = new byte[bytes.remaining()];\n            bytes.get(bibytes);\n\n            BigInteger bi = new BigInteger(bibytes);\n            return new BigDecimal(bi, scale);\n        }\n    }\n\n    /**\n     * This codec maps a CQL {@link DataType#cdouble()} to a Java {@link Double}.\n     */\n    private static class DoubleCodec extends PrimitiveDoubleCodec\n    {\n","sourceCodeStart":1324,"sourceCodeEnd":1360,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/cql3/functions/types/TypeCodec.java#L1324-L1360","documentation":"CQL decimal wire format is a 4-byte big-endian scale (varint exponent) followed by the unscaled BigInteger bytes. deserialize() requires at least 4 bytes to read the scale; fewer means the payload cannot be a decimal and the codec throws InvalidTypeException.","triggerScenarios":"Calling decimalCodec.deserialize(bytes, protocolVersion) where 0 < bytes.remaining() < 4 — e.g. decoding an int (4 bytes is OK, but 2-byte smallint isn't), float, or truncated buffer through the decimal codec.","commonSituations":"Schema changed from decimal to float/double/int while codecs assume decimal; truncated network or SSTable bytes; custom serialization writing only the unscaled bytes without the 4-byte scale prefix; misaligned buffer positions.","solutions":["Encode decimals properly: scale int followed by unscaled BigInteger.toByteArray() (see TypeCodec decimal serialize).","Match the codec to the column's actual type (double codec for 8-byte doubles, etc.).","Ensure the ByteBuffer position is at the value start (duplicate/rewind) so remaining() reflects the full value.","Replace truncated/partial payloads at the source — the data itself is corrupt if genuinely < 4 bytes."],"exampleFix":"// before\nByteBuffer b = ByteBuffer.allocate(2).putShort((short)3); // truncated\ndecimalCodec.deserialize(b, version); // throws\n// after\nbyte[] unscaled = new BigInteger(\"12345\").toByteArray();\nByteBuffer b = ByteBuffer.allocate(4 + unscaled.length).putInt(2).put(unscaled);\ndecimalCodec.deserialize(b, version);","handlingStrategy":"validation","validationCode":"if (bytes == null || bytes.remaining() < 4)\n    throw new IllegalArgumentException(\"decimal needs >= 4 bytes, got \" + (bytes == null ? 0 : bytes.remaining()));","typeGuard":"boolean isDecimalBuffer(ByteBuffer b) {\n    return b != null && b.remaining() >= 4;\n}","tryCatchPattern":"try {\n    return decimalCodec.deserialize(bytes, protocolVersion);\n} catch (InvalidTypeException e) {\n    throw new IllegalStateException(\"Corrupt or wrong-typed decimal payload: \" + bytes.remaining() + \" bytes\", e);\n}","preventionTips":["Always serialize decimals as 4-byte scale + unscaled BigInteger bytes (use the codec's own serialize).","Match codec to column type after any schema migration.","Duplicate/reset the buffer position before decoding so remaining() covers the whole value."],"tags":["cql","decimal","codec","deserialization","wire-format"],"backgroundTag":"shape-mismatch","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}