hibernate/hibernate-orm · error · MappingException
Association ' ' marked as '@NaturalId' is also annotated…
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.
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
- Could not parse discriminator value
- Entity ' ' has 'OptimisticLockType. ' but is not annotated…
- Entity ' ' has 'OptimisticLockType. ' but declares a…
- Ambiguous persistent property methods declared by
- An association from the table '" + getTable().getName() +…
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/75e7d9dba477f7e7.
Report an issue: GitHub.
Appendix: 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)