hibernate/hibernate-orm · error · HibernateException

Cannot interpret natural id value [%s] as compound natural i

Error message

Cannot interpret natural id value [%s] as compound natural id of entity '%s'

What it means

SimpleNaturalIdLoadAccess accepts a single value per load. verifySimplicity throws when the entity has a compound natural id and the supplied value is not one of the supported carriers for multiple values: an ordered array or List, a Map keyed by attribute name, or an instance of the natural-id class.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/loader/internal/SimpleNaturalIdLoadAccessImpl.java:111

		return doLoad( entityPersister().getNaturalIdMapping().normalizeInput( naturalIdValue) );
	}

	/**
	 * Verify that the given natural id is "simple".
	 * <p>
	 * We allow compound natural id "simple" loading if all the values are passed as an array,
	 * list, or map. We assume an array is properly ordered following the attribute ordering.
	 * For lists, just like arrays, we assume the user has ordered them properly; for maps,
	 * the key is expected to be the attribute name.
	 */
	private void verifySimplicity(Object naturalIdValue) {
		assert naturalIdValue != null;
		if ( !hasSimpleNaturalId
				&& !naturalIdValue.getClass().isArray()
				&& !(naturalIdValue instanceof List)
				&& !(naturalIdValue instanceof Map)
				&& ! ( isNaturalIdClass( naturalIdValue ) ) ) {
			throw new HibernateException(
					String.format(
							Locale.ROOT,
							"Cannot interpret natural id value [%s] as compound natural id of entity '%s'",
							naturalIdValue,
							entityPersister().getEntityName()
					)
			);
		}
	}

	private boolean isNaturalIdClass(Object naturalIdValue) {
		final EntityPersister entityPersister = entityPersister();
		return entityPersister.getNaturalIdMapping().getNaturalIdClass().isInstance(  naturalIdValue );
	}

	@Override
	public Optional<T> loadOptional(Object naturalIdValue) {
		return Optional.ofNullable( load( naturalIdValue ) );

View on GitHub (pinned to fad1729dce)

Solutions

  1. Switch to the explicit compound API: session.byNaturalId(Employee.class).using("email", e).using("dept", d).load().
  2. Or pass all values at once: an ordered List/array matching attribute order, or a Map keyed by attribute names.
  3. If the natural id is meant to be single-valued, remove the extra @NaturalId attribute.

Example fix

// before: compound natural id (email + department) loaded simply
Employee e = session.bySimpleNaturalId(Employee.class).load("john@acme.com");

// after
Employee e = session.byNaturalId(Employee.class)
        .using("email", "john@acme.com")
        .using("department", "ENG")
        .load();
Defensive patterns

Strategy: validation

Validate before calling

static boolean isSimpleNaturalId( Class<?> entity ) {
    return Arrays.stream( entity.getDeclaredFields() )
            .filter( f -> f.isAnnotationPresent( NaturalId.class ) )
            .count() == 1;
}
// use bySimpleNaturalId only when isSimpleNaturalId(entity) returns true

Prevention

When it happens

Trigger: session.bySimpleNaturalId(Employee.class).load("abc") where Employee declares @NaturalId on two or more fields; passing a scalar or an arbitrary POJO that is neither array, List, Map, nor the natural-id class.

Common situations: A second @NaturalId attribute is added later and breaks existing simple loads; teams unaware of the array/List/Map convention for compound ids; passing a DTO instead of the natural-id value object.

Related errors


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