hibernate/hibernate-orm · error · MappingException

unsaved-value NEGATIVE may only be used with short, int and

Error message

unsaved-value NEGATIVE may only be used with short, int and long types

What it means

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.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/engine/spi/VersionValue.java:85

		}
	};

	/**
	 * Assume the transient instance is newly instantiated if the version
	 * is negative, otherwise assume it is a detached instance.
	 */
	public static final VersionValue NEGATIVE = new VersionValue() {

		@Override
		public Boolean isUnsaved(@Nullable Object version) throws MappingException {
			CORE_LOGGER.versionUnsavedValueStrategy( "NEGATIVE" );
			if ( version == null ) {
				return Boolean.TRUE;
			}
			if ( version instanceof Number number ) {
				return number.longValue() < 0L;
			}
			throw new MappingException( "unsaved-value NEGATIVE may only be used with short, int and long types" );
		}

		@Override
		public Object getDefaultValue(@Nullable Object currentValue) {
			// FIXME this does not handle null values, and it's unknown how they should be handled.
			//   Probably worse, org.hibernate.mapping.SimpleValue.decodeNullValueSemantic is the only
			//   place where we define null value semantics, and it can never select "negatic" semantics,
			//   which means this implementation of VersionValue is likely not tested and even dead code.
			return IdentifierGeneratorHelper.makeIntegralValue( -1L, castNonNull( currentValue ).getClass() );
		}

		@Override
		public String toString() {
			return "VERSION_NEGATIVE";
		}
	};

	protected VersionValue() {

View on GitHub (pinned to fad1729dce)

Solutions

  1. Remove unsaved-value="negative" and rely on the modern default (undefined/null semantics with a version column)
  2. If negative-version semantics are truly required, make the version property a short, int, or long
  3. For timestamp versions, let Hibernate decide unsaved state via the version null check instead of the negative trick

Example fix

// before (hbm.xml legacy mapping)
<version name="version" column="ver" type="timestamp" unsaved-value="negative"/>

// after
<version name="version" column="ver" type="integer"/>  <!-- or drop unsaved-value and keep default -->
Defensive patterns

Strategy: validation

Validate before calling

Class<?> versionClass = versionValue.getClass();
if (!(versionClass == Short.class || versionClass == Integer.class || versionClass == Long.class)) {
    throw new MappingException("unsaved-value=negative requires short/int/long version, got " + versionClass.getName());
}

Type guard

static boolean supportsNegativeUnsavedValue(Object version) {
    return version == null
        || version instanceof Short || version instanceof Integer || version instanceof Long;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/98fbd5c56f29ac66. Report an issue: GitHub.