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
- Set the identifier before persist: entity.setId(...)
- If the id should be database-generated, add @GeneratedValue with the appropriate strategy
- For composite ids, verify every attribute is populated (including parts set by @PrePersist-adjacent code)
- 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
- Create entities only through factory methods that require the id
- Add @GeneratedValue when the database should generate the id
- For composite ids, verify every attribute is set before persist
- Assert non-null ids in save-path unit tests
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
- Null id generated for entity '
- Class '<componentClassName>' is an '@Embeddable' type and ma
- Property '<propertyName>' may not be annotated '@BatchSize'
- One to many association '<propertyName>' was annotated '@Col
- Collection '<propertyName>' was annotated '@Collate'
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/51566aa287101844.
Report an issue: GitHub.