{"record":{"id":"08992260aef8a25d","repo":"hibernate/hibernate-orm","slug":"unable-to-coerce-double-value-s-as-biginteger","errorCode":null,"errorMessage":"Unable to coerce Double value `%s` as BigInteger: not a whole number","messagePattern":"Unable to coerce Double value `(.+?)` as BigInteger: not a whole number","errorType":"exception","errorClass":"CoercionException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CoercionHelper.java","lineNumber":316,"sourceCode":"\t\t\t\t\t\t\t\"Unable to coerce Float value `%s` as Integer: not a whole number\",\n\t\t\t\t\t\t\tfloatValue\n\t\t\t\t\t)\n\t\t\t);\n\t\t}\n\t\treturn floatValue.longValue();\n\t}\n\n\tpublic static Long toLong(BigInteger value) {\n\t\treturn coerceWrappingError( value::longValueExact );\n\t}\n\n\tpublic static Long toLong(BigDecimal value) {\n\t\treturn coerceWrappingError( value::longValueExact );\n\t}\n\n\tpublic static BigInteger toBigInteger(Double doubleValue) {\n\t\tif ( ! isWholeNumber( doubleValue ) ) {\n\t\t\tthrow new CoercionException(\n\t\t\t\t\tString.format(\n\t\t\t\t\t\t\tLocale.ROOT,\n\t\t\t\t\t\t\t\"Unable to coerce Double value `%s` as BigInteger: not a whole number\",\n\t\t\t\t\t\t\tdoubleValue\n\t\t\t\t\t)\n\t\t\t);\n\t\t}\n\t\treturn BigInteger.valueOf( doubleValue.longValue() );\n\t}\n\n\tpublic static BigInteger toBigInteger(Float floatValue) {\n\t\tif ( ! isWholeNumber( floatValue ) ) {\n\t\t\tthrow new CoercionException(\n\t\t\t\t\tString.format(\n\t\t\t\t\t\t\tLocale.ROOT,\n\t\t\t\t\t\t\t\"Unable to coerce Double Float `%s` as BigInteger: not a whole number\",\n\t\t\t\t\t\t\tfloatValue\n\t\t\t\t\t)","sourceCodeStart":298,"sourceCodeEnd":334,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CoercionHelper.java#L298-L334","documentation":"Hibernate throws this CoercionException when a Double with a fractional part is coerced to BigInteger. CoercionHelper.toBigInteger(Double) checks isWholeNumber(doubleValue) and rejects values like 1e10 + 0.5 before building the BigInteger via BigInteger.valueOf(longValue()). It is reached from BigIntegerJavaType.coerce when a Double value is supplied to a BigInteger-mapped attribute. Hibernate blocks the lossy fractional-to-integral conversion instead of truncating toward zero.","triggerScenarios":"`BigIntegerJavaType.coerce(1234.56)` — assigning a Double to a BigInteger entity field via a Number/Object-typed setter, or binding a fractional double as a query parameter against a BigInteger-typed path; typical with ids (Snowflake-style), large counters, or monetary magnitudes computed as double.","commonSituations":"Snowflake/ULID ids serialized as Double in JSON (losing precision AND failing whole-number check) then mapped onto BigInteger fields; financial computations in double feeding NUMERIC-mapped BigInteger attributes; JavaScript frontends sending large ids as floats; Jackson loosely typed deserialization into Object (Double) copied onto BigInteger properties.","solutions":["Fix serialization end to end: emit big ids as JSON strings or integral numbers and type DTO fields BigInteger/Long so they never pass through double.","Round explicitly if truncation is acceptable: `BigInteger.valueOf(Math.round(d))` — but for ids > 2^53 double already lost precision, so repair the source data instead.","Validate whole-number-ness of incoming Numbers before assigning to BigInteger attributes.","Store genuinely fractional magnitudes in BigDecimal with a DECIMAL column, not BigInteger."],"exampleFix":"// before\nObject rawId = jsonResponse.get(\"transactionId\"); // Double 3.52e18 with fraction/precision loss\ntxn.setTxnId(rawId);                              // BigInteger field -> \"not a whole number\"\n\n// after\n// server sends ids as strings: {\"transactionId\":\"3520000000000000001\"}\ntxn.setTxnId(new BigInteger((String) jsonResponse.get(\"transactionId\")));\n// never route big ids through double/Double","handlingStrategy":"validation","validationCode":"Object raw = response.get(\"transactionId\");\nif (raw instanceof Double d) {\n    if (d != Math.rint(d) || Math.abs(d) >= 9.007199254740992E15) {\n        throw new IllegalArgumentException(\"id lost whole-number/precision status in double: \" + d);\n    }\n    txn.setTxnId(BigInteger.valueOf(d.longValue()));\n} else if (raw instanceof String s) {\n    txn.setTxnId(new BigInteger(s));\n} else {\n    txn.setTxnId((BigInteger) raw);\n}","typeGuard":"static boolean isExactBigIntegerCandidate(Object o) { return o instanceof BigInteger || o instanceof Long l || (o instanceof String s && s.matches(\"-?\\\\d+\")); }","tryCatchPattern":"catch CoercionException and reject the payload — for big ids the double representation has already lost precision, so recovery requires re-reading the original source, not rounding.","preventionTips":["Never serialize ids larger than 2^53 as JSON numbers; use strings.","Type DTO id fields BigInteger/Long, never Object/Number/Double.","Validate id fields lexically (digits only) at the boundary.","Prefer BigDecimal mappings for monetary magnitudes with fractions."],"tags":["hibernate","type-coercion","biginteger","double","fractional","precision-loss"],"backgroundTag":"fractional-to-integer-coercion","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}