hibernate/hibernate-orm · error · IllegalArgumentException

Given entity [{}] does not define natural-id

Error message

Given entity [{}] does not define natural-id

What it means

Thrown by Statistics#getNaturalIdStatistics(String) when the named entity has no natural id in its mapping. Hibernate looks up the EntityPersister and checks hasNaturalIdentifier() before building NaturalIdStatisticsImpl; natural-id resolution and cache counters only exist for entities that declare @NaturalId (or <natural-id/>), so the request is rejected with an IllegalArgumentException instead of returning empty counters.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/stat/internal/StatisticsImpl.java:1007

				",update timestamps cache misses=" + updateTimestampsCacheMissCount +
				",max query time=" + queryExecutionMaxTime +
				",query plan cache hits=" + queryPlanCacheHitCount +
				",query plan cache misses=" + queryPlanCacheMissCount +
				']';
	}

	private EntityStatisticsImpl instantiateEntityStatistics(final String entityName) {
		return new EntityStatisticsImpl( metamodel.getEntityDescriptor( entityName ) );
	}

	private CollectionStatisticsImpl instantiateCollectionStatistics(final String role) {
		return new CollectionStatisticsImpl( metamodel.getCollectionDescriptor( role ) );
	}

	private NaturalIdStatisticsImpl instantiateNaturalStatistics(final String entityName) {
		final EntityPersister entityDescriptor = metamodel.getEntityDescriptor( entityName );
		if ( !entityDescriptor.hasNaturalIdentifier() ) {
			throw new IllegalArgumentException( "Given entity [" + entityName + "] does not define natural-id" );
		}
		return new NaturalIdStatisticsImpl( entityDescriptor );
	}

	private CacheRegionStatisticsImpl instantiateCacheRegionStatistics(final String regionName) {
		final Region region = cache.getRegion( regionName );
		if ( region == null ) {
			throw new IllegalArgumentException( "Unknown cache region : " + regionName );
		}
		if ( region instanceof QueryResultsRegion ) {
			throw new IllegalArgumentException(
					"Region name [" + regionName + "] referred to a query result region, not a domain data region"
			);
		}
		return new CacheRegionStatisticsImpl( region );
	}

	private CacheRegionStatisticsImpl instantiateCacheRegionStatsForQueryResults(final String regionName) {

View on GitHub (pinned to fad1729dce)

Solutions

  1. If the entity should have a natural id, map one: annotate the immutable, unique properties with @NaturalId (or add <natural-id/> in hbm.xml).
  2. Guard the call: request natural-id statistics only for entities whose EntityPersister#hasNaturalIdentifier() returns true.
  3. Verify the entity-name string (FQN or explicit entity name) so it resolves to the entity you expect.

Example fix

// before
NaturalIdStatistics stats = statistics.getNaturalIdStatistics(User.class.getName());
// throws IllegalArgumentException: User has no natural id

// after
SessionFactoryImplementor impl = sessionFactory.unwrap(SessionFactoryImplementor.class);
EntityPersister user = impl.getRuntimeMetamodel().getEntityDescriptor(User.class.getName());
NaturalIdStatistics stats = user.hasNaturalIdentifier()
        ? statistics.getNaturalIdStatistics(User.class.getName())
        : null;
Defensive patterns

Strategy: validation

Validate before calling

EntityPersister p = sessionFactory.unwrap(SessionFactoryImplementor.class)
        .getRuntimeMetamodel().getEntityDescriptor(entityName);
if (p.hasNaturalIdentifier()) {
    NaturalIdStatistics stats = statistics.getNaturalIdStatistics(entityName);
}

Try / catch

try {
    NaturalIdStatistics stats = statistics.getNaturalIdStatistics(entityName);
} catch (IllegalArgumentException e) {
    // entity has no natural id — skip it, do not retry
    log.debug("No natural-id statistics for {}", entityName);
}

Prevention

When it happens

Trigger: Calling statistics.getNaturalIdStatistics("com.acme.User") (or the natural-id cache statistics accessor) for an entity whose mapping has no @NaturalId property and no <natural-id/> element: instantiateNaturalStatistics resolves the descriptor, sees hasNaturalIdentifier() == false, and throws before constructing the statistics object.

Common situations: A monitoring dashboard iterates every entity name from the metamodel and unconditionally asks for natural-id statistics; the call was written against an entity that later lost its @NaturalId mapping; the entity-name string resolves to a different, natural-id-free entity.

Related errors


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