hibernate/hibernate-orm · error · IllegalArgumentException
null key for collection: {}
Error message
null key for collection: {} What it means
BasicCollectionPersister.applyInsertRowValues decomposes the collection's FK key value into JDBC bindings when writing a collection row; a null key is illegal because every row must reference its owner, so it throws IllegalArgumentException('null key for collection: ' + fullRole). It surfaces at flush time during collection recreate/insert.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/persister/collection/BasicCollectionPersister.java:365
final var pluralAttribute = getAttributeMapping();
assert pluralAttribute != null;
final var foreignKeyDescriptor = pluralAttribute.getKeyDescriptor();
assert foreignKeyDescriptor != null;
final var insertBuilder = new TableInsertBuilderStandard( this, tableReference, getFactory() );
applyInsertDetails( insertBuilder );
//noinspection unchecked,rawtypes
return (TableMutation) insertBuilder.buildMutation();
}
private void applyInsertRowValues(
PersistentCollection<?> collection,
Object key,
Object rowValue,
int rowPosition,
SharedSessionContractImplementor session,
JdbcValueBindings jdbcValueBindings) {
if ( key == null ) {
throw new IllegalArgumentException( "null key for collection: " + getNavigableRole().getFullPath() );
}
final var attributeMapping = getAttributeMapping();
attributeMapping.getKeyDescriptor().getKeyPart().decompose(
key,
0,
jdbcValueBindings,
null,
DEFAULT_VALUE_SETTER,
session
);
final var identifierDescriptor = attributeMapping.getIdentifierDescriptor();
if ( identifierDescriptor != null ) {
identifierDescriptor.decompose(
collection.getIdentifier( rowValue, rowPosition ),
0,
jdbcValueBindings,
null,View on GitHub (pinned to fad1729dce)
Solutions
- Persist the owning entity before or with the collection: enable cascade (CascadeType.PERSIST/ALL) or save the owner first so its key is non-null at flush
- Fix the collection key mapping: map the FK to the owner's id column(s) / non-null property, and make the join column non-nullable if the model guarantees it
- Never reassign a PersistentCollection instance from one owner to another — create a new collection on the new owner and save the owner first
- In tests/fixtures, set the owner's generated/assigned id before adding elements and flushing
Example fix
// before Order order = new Order(); // transient, no id, no cascade List<OrderLine> lines = otherOrder.getLines(); // detached collection order.setLines(lines); em.persist(order); // em.persist(order) only -> flush writes collection rows with null key // after @OneToMany(mappedBy = "order", cascade = CascadeType.ALL) private List<OrderLine> lines = new ArrayList<>(); // copy elements, own them, cascade persists owner before rows Order order = new Order(); otherOrder.getLines().forEach(l -> order.addLine(l)); em.persist(order);
Defensive patterns
Strategy: try-catch
Validate before calling
// Before flush: the owner must be persisted so the collection FK resolves non-null
if (owner.getId() == null) {
session.persist(owner); // or ensure cascade = PERSIST/ALL on the collection
}
// avoid reusing detached PersistentCollection instances across owners
if (lines instanceof org.hibernate.collection.spi.PersistentCollection<?> pc
&& pc.getOwner() != null && pc.getOwner() != owner) {
owner.setLines(new ArrayList<>(lines)); // fresh collection owned by 'owner'
} Try / catch
try {
session.persist(owner);
session.flush();
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().startsWith("null key for collection")) {
// collection FK was null at row-insert time: save/attach the owner (cascade), fix join-column mapping, then retry
log.warn("Null collection key for {} — persisting owner and retrying", e.getMessage());
throw e; // after fixing owner state, a new flush attempt is safe
}
throw e;
} Prevention
- Enable CascadeType.PERSIST/ALL on owning-side collections or persist the owner before adding children
- Never move a PersistentCollection between entities — build a new collection on the new owner
- Map collection FK join columns to non-nullable owner id columns and assert the owner id is set before flush
When it happens
Trigger: Flushing a collection whose owner key resolves to null: the owning instance is transient and not yet persisted (no cascade, no id), the FK is mapped from a property that is null at flush (nullable join column / property-ref style key), or a detached collection instance was attached to a new, unsaved owner.
Common situations: Missing CascadeType.PERSIST/ALL so the owner never gets saved before the collection row write; manually assigning a collection from one entity to a fresh entity without saving it; join-column mappings referencing a nullable or unset property; test fixtures constructing object graphs that skip the owner's id.
Related errors
- Instance of '" + entityName + "' references an unsaved trans
- Instance of '%s' references an unsaved transient instance of
- Unbreakable cycle detected for SCC: %s
- There are delayed insert actions before operation as cascade
- Flush during cascade is dangerous
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/f7b644c3b63c2f5b.
Report an issue: GitHub.