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

  1. Remove @NotFound(IGNORE) from the association (default is EXCEPTION) and fix the data/FK so targets exist
  2. Drop @NaturalId from that association and choose a different natural-id property
  3. 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

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


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