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
- Switch to the explicit compound API: session.byNaturalId(Employee.class).using("email", e).using("dept", d).load().
- Or pass all values at once: an ordered List/array matching attribute order, or a Map keyed by attribute names.
- 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
- Wrap natural-id lookups in a repository method that knows the entity's key shape
- Document the array/List/Map convention for compound natural ids loaded via the simple API
- Add a mapping test asserting the number of @NaturalId attributes per entity
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
- immutable global instance of LockOptions
- Table '${table}' has no column named '${column}' matching th
- Property '${property}' belongs to an entity subclass and may
- Caching was not configured for entity natural id:
- Custom event-type name must be non-null.
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/d1ec27f5d0d84a94.
Report an issue: GitHub.