{"record":{"id":"98fbd5c56f29ac66","repo":"hibernate/hibernate-orm","slug":"unsaved-value-negative-may-only-be-used-with-short","errorCode":null,"errorMessage":"unsaved-value NEGATIVE may only be used with short, int and long types","messagePattern":"unsaved-value NEGATIVE may only be used with short, int and long types","errorType":"exception","errorClass":"MappingException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/engine/spi/VersionValue.java","lineNumber":85,"sourceCode":"\t\t}\n\t};\n\n\t/**\n\t * Assume the transient instance is newly instantiated if the version\n\t * is negative, otherwise assume it is a detached instance.\n\t */\n\tpublic static final VersionValue NEGATIVE = new VersionValue() {\n\n\t\t@Override\n\t\tpublic Boolean isUnsaved(@Nullable Object version) throws MappingException {\n\t\t\tCORE_LOGGER.versionUnsavedValueStrategy( \"NEGATIVE\" );\n\t\t\tif ( version == null ) {\n\t\t\t\treturn Boolean.TRUE;\n\t\t\t}\n\t\t\tif ( version instanceof Number number ) {\n\t\t\t\treturn number.longValue() < 0L;\n\t\t\t}\n\t\t\tthrow new MappingException( \"unsaved-value NEGATIVE may only be used with short, int and long types\" );\n\t\t}\n\n\t\t@Override\n\t\tpublic Object getDefaultValue(@Nullable Object currentValue) {\n\t\t\t// FIXME this does not handle null values, and it's unknown how they should be handled.\n\t\t\t//   Probably worse, org.hibernate.mapping.SimpleValue.decodeNullValueSemantic is the only\n\t\t\t//   place where we define null value semantics, and it can never select \"negatic\" semantics,\n\t\t\t//   which means this implementation of VersionValue is likely not tested and even dead code.\n\t\t\treturn IdentifierGeneratorHelper.makeIntegralValue( -1L, castNonNull( currentValue ).getClass() );\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn \"VERSION_NEGATIVE\";\n\t\t}\n\t};\n\n\tprotected VersionValue() {","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/engine/spi/VersionValue.java#L67-L103","documentation":"VersionValue.NEGATIVE implements the legacy 'unsaved-value=negative' strategy: an entity is considered unsaved when its version is a negative number. isUnsaved() only handles null (unsaved) and Number values; any other non-null version object proves the strategy was configured for an unsupported attribute type, so a MappingException is thrown. The in-code FIXME notes modern mapping cannot even select NEGATIVE anymore, so hitting this means legacy or hand-built configuration.","triggerScenarios":"unsaved-value=\"negative\" on a version property whose type is not numeric — e.g. a Timestamp/Date, String, or user type version — so isUnsaved() receives a non-null, non-Number object at flush/load time.","commonSituations":"Porting Hibernate 2/3-era mappings where unsaved-value=negative was used with timestamp optimistic-lock values; hand-constructing VersionValue.NEGATIVE; old hbm.xml files copied into a modern project.","solutions":["Remove unsaved-value=\"negative\" and rely on the modern default (undefined/null semantics with a version column)","If negative-version semantics are truly required, make the version property a short, int, or long","For timestamp versions, let Hibernate decide unsaved state via the version null check instead of the negative trick"],"exampleFix":"// before (hbm.xml legacy mapping)\n<version name=\"version\" column=\"ver\" type=\"timestamp\" unsaved-value=\"negative\"/>\n\n// after\n<version name=\"version\" column=\"ver\" type=\"integer\"/>  <!-- or drop unsaved-value and keep default -->","handlingStrategy":"validation","validationCode":"Class<?> versionClass = versionValue.getClass();\nif (!(versionClass == Short.class || versionClass == Integer.class || versionClass == Long.class)) {\n    throw new MappingException(\"unsaved-value=negative requires short/int/long version, got \" + versionClass.getName());\n}","typeGuard":"static boolean supportsNegativeUnsavedValue(Object version) {\n    return version == null\n        || version instanceof Short || version instanceof Integer || version instanceof Long;\n}","tryCatchPattern":null,"preventionTips":["Prefer modern unsaved-value semantics (undefined/null) over 'negative'","Reserve unsaved-value=negative for numeric version columns only","Run mapping tests at boot for legacy hbm.xml migrations"],"tags":["hibernate","mapping","versioning","unsaved-value","legacy-migration"],"backgroundTag":"invalid-mapping-configuration","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}