hibernate/hibernate-orm · error · IllegalArgumentException

null key for collection: %s

Error message

null key for collection: %s

What it means

While planning collection-row INSERTs, BasicCollectionDecomposer.bindInsertRowValues (BasicCollectionDecomposer.java:1265) must bind the owner's collection key (the FK to the owner). If that key is null it throws IllegalArgumentException("null key for collection: <role>"). The owner key derives from the owning entity's identifier, so a null key means the collection is being flushed with no owner id attached - the owner was never saved, its id was never assigned, or the key was not carried through the flush plan.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/action/queue/internal/decompose/collection/BasicCollectionDecomposer.java:1265

			insertBuilder.addColumnAssignment( temporalMapping.createStartingValueBinding( startingColumn ) );

			final var endingColumn = new ColumnReference(
					insertBuilder.getMutatingTable(),
					temporalMapping.getEndingColumnMapping()
			);
			insertBuilder.addColumnAssignment( temporalMapping.createNullEndingValueBinding( endingColumn ) );
		}
	}

	private void bindInsertRowValues(
			PersistentCollection<?> collection,
			Object key,
			Object rowValue,
			int rowPosition,
			SharedSessionContractImplementor session,
			JdbcValueBindings jdbcValueBindings) {
		if ( key == null ) {
			throw new IllegalArgumentException( "null key for collection: "
					+ persister.getNavigableRole().getFullPath() );
		}

		final var attributeMapping = persister.getAttributeMapping();
		attributeMapping.getKeyDescriptor().getKeyPart().decompose(
				key,
				jdbcValueBindings::bindAssignment,
				session
		);

		final var identifierDescriptor = attributeMapping.getIdentifierDescriptor();
		if ( identifierDescriptor != null ) {
			identifierDescriptor.decompose(
					collection.getIdentifier( rowValue, rowPosition ),
					jdbcValueBindings::bindAssignment,
					session
			);
		}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Ensure the owning entity is persisted and has a non-null id before the collection flushes: persist the owner (cascade from a parent) and keep the association bidirectional so cascade reaches it
  2. If ids are assigned manually, verify every persist path sets the id - add a @PrePersist assertion or factory method that always assigns it
  3. Check custom IdentifierGenerators for branches that can return null and fix or throw there with a clear message
  4. As a diagnostic, set hibernate.flush.queue.type=legacy: if the same data fails there too it is your data/mapping; if only the graph queue fails, report the planner gap

Example fix

// before - owner never persisted, flush of its collection has null key
User u = new User();           // no id assigned (manual generator)
u.getTags().add(tag);
session.persist(tag);           // only the element saved
session.flush();                // -> "null key for collection: User.tags"

// after - persist the owner so the key exists, cascade elements
session.persist(u);             // id assigned / owner managed
u.getTags().add(tag);
session.flush();
Defensive patterns

Strategy: validation

Validate before calling

// ensure the owner has an id before its collection can flush
if (session.getIdentifier(owner) == null && !session.contains(owner)) {
    session.persist(owner); // assigns/queues the id and makes the owner managed
}
owner.getItems().add(item);

Try / catch

try {
    session.flush();
} catch (IllegalArgumentException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("null key for collection")) {
        // owner id missing: persist the owner, then retry the unit of work
    } else throw e;
}

Prevention

When it happens

Trigger: A collection becomes dirty on an entity whose identifier is still null at flush time (assigned-id generator and the id was never set, or a generator that yields null); adding elements to a new entity's collection and flushing before the owner is persisted; detached collection instances reused without an owner.

Common situations: @GeneratedValue never configured and code forgets to set the manual id; custom IdentifierGenerator returning null in some branch; cascading elements into a collection while the owner itself is transient because cascade is missing on the owning side; partial test fixtures that flush too early.

Related errors


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