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
- 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
- If ids are assigned manually, verify every persist path sets the id - add a @PrePersist assertion or factory method that always assigns it
- Check custom IdentifierGenerators for branches that can return null and fix or throw there with a clear message
- 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
- Always persist the owning entity before (or via cascade together with) its elements
- For assigned ids, set them in one factory method and assert non-null in @PrePersist
- Custom IdentifierGenerators must never return null - throw IdentifierGenerationException instead
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
- null key for collection: %s
- null key for collection: %s
- cannot recreate collection while filter is enabled [%s : %s]
- cannot recreate collection while filter is enabled: " + coll
- Decomposition not supported for %s
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/f5cb3f1c90a02861.
Report an issue: GitHub.