hibernate/hibernate-orm · error · IdentifierGenerationException

Null id generated for entity '

Error message

Null id generated for entity '

What it means

When an entity has a composite id with values generated before execution (CompositeNestedGeneratedValueGenerator, e.g. @GeneratedValue attributes inside an @EmbeddedId), AbstractSaveEventListener pre-generates the id instance during persist. If that generation returns null it throws IdentifierGenerationException('Null id generated for entity ...'): one of the nested per-attribute generators, or the mapping around it, produced no value.

Source

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

	protected Object saveWithGeneratedId(
			@Nonnull Object entity,
			@Nullable String entityName,
			@Nonnull C context,
			@Nonnull EventSource source,
			boolean requiresImmediateIdAccess) {
		final var persister = source.getEntityPersister( entityName, entity );
		final var generator = persister.getGenerator();
		final boolean generatedOnExecution = generator.generatedOnExecution( entity, source );
		final boolean generatedBeforeExecution = generator.generatedBeforeExecution( entity, source );
		final Object generatedId;
		if ( generatedOnExecution ) {
			if ( generatedBeforeExecution
					&& generator instanceof CompositeNestedGeneratedValueGenerator compositeGenerator ) {
				// for a composite id, we might need to
				// create the composite id instance early
				final Object preGeneratedId = compositeGenerator.generate( source, entity );
				if ( preGeneratedId == null ) {
					throw new IdentifierGenerationException(
							"Null id generated for entity '" + persister.getEntityName() + "'" );
				}
				persister.setIdentifier( entity, preGeneratedId, source );
			}
			// the id gets generated by the database and is
			// not yet available
			generatedId = null;
		}
		else if ( !generator.generatesOnInsert() ) {
			// get it from the entity later, since we need
			// the @PrePersist callback to happen first
			generatedId = null;
		}
		else if ( generatedBeforeExecution ) {
			// go ahead and generate id, and then set it to
			// the entity instance, so it will be available
			// to the entity in the @PrePersist callback
			generatedId = generateId( entity, source, (BeforeExecutionGenerator) generator, persister );

View on GitHub (pinned to fad1729dce)

Solutions

  1. Review each @GeneratedValue attribute inside the composite id and replace custom generators with built-ins (sequence, uuid) or fix their null path
  2. Set every manually-assigned attribute of the composite id before calling persist()
  3. Write a minimal persist test for the entity to identify which id attribute generates null
  4. If the mapping mixes assigned and generated parts awkwardly, restructure to a single generated @Id or a fully-assigned composite id

Example fix

// before: composite id with a custom generator that can return null
@Embeddable
class OrderId {
    @GeneratedValue(generator = "myGen")
    Long number;
    String tenant;
}

// after: built-in sequence for the generated part, assigned part set before persist
@Embeddable
class OrderId {
    @GeneratedValue(generator = "order_seq")
    @SequenceGenerator(name = "order_seq", sequenceName = "order_seq", allocationSize = 50)
    Long number;
    String tenant;
}
order.setId(new OrderId(null, "acme"));
Defensive patterns

Strategy: validation

Validate before calling

// before persist(): every assigned attribute of the composite id must be non-null
OrderId id = order.getId();
if (id == null || id.getTenant() == null) { // list all assigned parts
    throw new IllegalStateException("assigned parts of the composite id must be set before persist()");
}
em.persist(order);

Type guard

static boolean isCompositeIdComplete(OrderId id) {
    return id != null && id.getTenant() != null; // check each assigned part
}

Try / catch

try {
    em.persist(order);
} catch (IdentifierGenerationException e) {
    // identify which @GeneratedValue attribute inside the composite id produced null
    throw new IllegalStateException("composite id generation failed for " + order.getClass().getName(), e);
}

Prevention

When it happens

Trigger: A @GeneratedValue attribute inside a composite id whose generator can return null (custom generator with an unhandled path); the manually-assigned part of the composite is null so the nested generator cannot assemble the id; generator type mismatched to the attribute type (e.g. a UUID strategy feeding a numeric field).

Common situations: Entities with @EmbeddedId/@IdClass containing @GeneratedValue members migrated to newer Hibernate where composite pre-generation semantics changed; custom BeforeExecutionGenerator implementations embedded in composite ids; test fixtures persisting entities without initializing the assigned part of the id.

Related errors


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