hibernate/hibernate-orm · error · IdentifierGenerationException

Identifier of entity '

Error message

Identifier of entity '

What it means

The entity's id is mapped as manually assigned (no generator that generates on insert — a plain @Id without @GeneratedValue, or an assigned generator). When persist() runs, AbstractSaveEventListener reads the identifier from the entity, finds null, and throws IdentifierGenerationException: the identifier must be set by application code before calling persist().

Source

Thrown at hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java:217

			boolean useIdentityColumn,
			@Nonnull C context,
			@Nonnull EventSource source,
			boolean delayIdentityInserts) {

		// call this after generation of an id,
		// but before we retrieve an assigned id
		source.runEntityLifecycleCallback( () -> persister.getEntityCallbacks().preCreate( entity ) );

		processIfSelfDirtinessTracker( entity, SelfDirtinessTracker::$$_hibernate_clearDirtyAttributes );
		processIfManagedEntity( entity, managedEntity -> managedEntity.$$_hibernate_setUseTracker( true ) );

		final var generator = persister.getGenerator();
		if ( !generator.generatesOnInsert()
				|| generator instanceof CompositeNestedGeneratedValueGenerator compositeGenerator
						&& compositeGenerator.hasAssignedValues() ) {
			id = persister.getIdentifier( entity, source );
			if ( id == null ) {
				throw new IdentifierGenerationException( "Identifier of entity '" + persister.getEntityName()
						+ "' must be manually assigned before calling 'persist()'" );
			}
		}

		if ( EVENT_LISTENER_LOGGER.isTraceEnabled() ) {
			EVENT_LISTENER_LOGGER.persisting(
					infoString( persister, id, source.getFactory() ) );
		}

		final EntityKey key;
		if ( useIdentityColumn ) {
			key = null;
		}
		else {
			assert id != null;
			key = entityKey( id, persister, source );
		}
		return performSaveOrReplicate( entity, key, persister, useIdentityColumn, context, source, delayIdentityInserts );

View on GitHub (pinned to fad1729dce)

Solutions

  1. Set the identifier before persist: entity.setId(...)
  2. If the id should be database-generated, add @GeneratedValue with the appropriate strategy
  3. For composite ids, verify every attribute is populated (including parts set by @PrePersist-adjacent code)
  4. Add a factory method for the entity that requires the id so callers cannot forget it

Example fix

// before
em.persist(new Customer("Acme")); // Customer.id is @Id without @GeneratedValue

// after
Customer c = new Customer("Acme");
c.setId(nextCustomerId());
em.persist(c);
Defensive patterns

Strategy: validation

Validate before calling

if (entity.getId() == null) {
    throw new IllegalArgumentException("id must be assigned before persist() for " + entity.getClass().getName());
}
em.persist(entity);

Type guard

static boolean hasAssignedId(Customer c) {
    return c != null && c.getId() != null;
}

Try / catch

try {
    em.persist(entity);
} catch (IdentifierGenerationException e) {
    // message contains 'must be manually assigned'
    throw new IllegalArgumentException("persist called without an assigned id", e);
}

Prevention

When it happens

Trigger: em.persist()/session.persist() on an entity whose @Id field (no @GeneratedValue) is null; @IdClass/@EmbeddedId mappings where at least one attribute is unset; an entity assembled by a mapper or builder that skipped the id.

Common situations: DTO-to-entity mappings forgetting to copy the business key used as id; new entity types introduced without id generation; refactoring @GeneratedValue away without updating call sites.

Related errors


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