hibernate/hibernate-orm · error · MappingException
Association '{propertyName}' marked as '@NaturalId' is also
Error message
Association '{propertyName}' marked as '@NaturalId' is also annotated '@NotFound(IGNORE)' What it means
BaseEntityPersister.verifyNaturalIdProperty inspects every property of the natural id, recursing into @Embedded components. A @NaturalId must be reliably resolvable, but @NotFound(action = IGNORE) makes a @ManyToOne tolerate a missing target row by loading null, which silently breaks natural-id resolution and caching, so the mapping is rejected with MappingException at boot. Because of the recursion, the combination inside an embeddable member of the natural id fails too.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/persister/entity/BaseEntityPersister.java:563
collectionsInDefaultFetchGroupEnabled,
creationContext.getMetadata()
);
}
private static boolean writePropertyValue(OnExecutionGenerator generator, EventType eventType) {
final boolean writePropertyValue = generator.writePropertyValue( eventType );
// TODO: move this validation somewhere else!
// if ( !writePropertyValue && generator instanceof BeforeExecutionGenerator ) {
// throw new HibernateException( "BeforeExecutionGenerator returned false from OnExecutionGenerator.writePropertyValue()" );
// }
return writePropertyValue;
}
private void verifyNaturalIdProperty(Property property) {
final var value = property.getValue();
if ( value instanceof ManyToOne toOne ) {
if ( toOne.getNotFoundAction() == NotFoundAction.IGNORE ) {
throw new MappingException( "Association '" + propertyName( property )
+ "' marked as '@NaturalId' is also annotated '@NotFound(IGNORE)'"
);
}
}
else if ( value instanceof Component component ) {
for ( var componentProperty : component.getProperties() ) {
verifyNaturalIdProperty( componentProperty );
}
}
}
private String propertyName(Property property) {
return getName() + "." + property.getName();
}
private static Generator buildGenerator(
final String entityName,
final Property mappingProperty,View on GitHub (pinned to fad1729dce)
Solutions
- Remove @NotFound(IGNORE) from the association (default is EXCEPTION) and fix the data/FK so targets exist
- Drop @NaturalId from that association and choose a different natural-id property
- If the association is genuinely optional it cannot be a natural id: remove it from the natural-id set or embeddable
Example fix
// before @ManyToOne(fetch = LAZY) @NaturalId @NotFound(action = NotFoundAction.IGNORE) private User owner; // after @ManyToOne(fetch = LAZY) @NaturalId private User owner;
Defensive patterns
Strategy: validation
Validate before calling
static void checkNaturalIdMembers(Class<?> entity) {
for ( java.lang.reflect.Field f : entity.getDeclaredFields() ) {
if ( f.isAnnotationPresent(NaturalId.class)
&& f.isAnnotationPresent(NotFound.class)
&& f.getAnnotation(NotFound.class).action() == NotFoundAction.IGNORE ) {
throw new IllegalStateException("@NaturalId member '" + f.getName()
+ "' must not combine @NotFound(IGNORE)");
}
}
// recurse into embeddable members used in the natural id, the persister check does too
} Try / catch
try {
sessionFactory = metadata.getSessionFactoryBuilder().build();
}
catch ( org.hibernate.MappingException e ) {
throw new IllegalStateException("SessionFactory boot failed: " + e.getMessage(), e);
} Prevention
- Keep natural ids restricted to scalar fields and fully enforced (NOT NULL FK) associations
- Avoid @NotFound(IGNORE) on new mappings; fix the data or the FK instead
- When reusing embeddables in a natural id, check their members for @NotFound too
When it happens
Trigger: @ManyToOne @NaturalId @NotFound(action = NotFoundAction.IGNORE) on an association; the same combination nested inside an embeddable that is (part of) the natural id (component.getProperties() recursion).
Common situations: Retrofitting natural-id lookup onto legacy optional associations; using @NotFound(IGNORE) to tolerate broken or unenforced foreign keys; reusing an existing embeddable that already carried @NotFound as a natural-id component.
Related errors
- Entity '${name}' has 'OptimisticLockType.${optimisticLockSty
- Entity '{name}' has 'OptimisticLockType.{optimisticLockStyle
- Could not parse discriminator value '{discriminatorValue}' a
- Identifier property '" + getPath( holder, data ) + "' cannot
- Attribute '%s' of entity '%s' is mapped by association '%s'
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/75e7d9dba477f7e7.
Report an issue: GitHub.