{"record":{"id":"f7b644c3b63c2f5b","repo":"hibernate/hibernate-orm","slug":"null-key-for-collection","errorCode":null,"errorMessage":"null key for collection: {}","messagePattern":"null key for collection: (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/persister/collection/BasicCollectionPersister.java","lineNumber":365,"sourceCode":"\t\tfinal var pluralAttribute = getAttributeMapping();\n\t\tassert pluralAttribute != null;\n\t\tfinal var foreignKeyDescriptor = pluralAttribute.getKeyDescriptor();\n\t\tassert foreignKeyDescriptor != null;\n\t\tfinal var insertBuilder = new TableInsertBuilderStandard( this, tableReference, getFactory() );\n\t\tapplyInsertDetails( insertBuilder );\n\t\t//noinspection unchecked,rawtypes\n\t\treturn (TableMutation) insertBuilder.buildMutation();\n\t}\n\n\tprivate void applyInsertRowValues(\n\t\t\tPersistentCollection<?> collection,\n\t\t\tObject key,\n\t\t\tObject rowValue,\n\t\t\tint rowPosition,\n\t\t\tSharedSessionContractImplementor session,\n\t\t\tJdbcValueBindings jdbcValueBindings) {\n\t\tif ( key == null ) {\n\t\t\tthrow new IllegalArgumentException( \"null key for collection: \" + getNavigableRole().getFullPath() );\n\t\t}\n\t\tfinal var attributeMapping = getAttributeMapping();\n\t\tattributeMapping.getKeyDescriptor().getKeyPart().decompose(\n\t\t\t\tkey,\n\t\t\t\t0,\n\t\t\t\tjdbcValueBindings,\n\t\t\t\tnull,\n\t\t\t\tDEFAULT_VALUE_SETTER,\n\t\t\t\tsession\n\t\t);\n\n\t\tfinal var identifierDescriptor = attributeMapping.getIdentifierDescriptor();\n\t\tif ( identifierDescriptor != null ) {\n\t\t\tidentifierDescriptor.decompose(\n\t\t\t\t\tcollection.getIdentifier( rowValue, rowPosition ),\n\t\t\t\t\t0,\n\t\t\t\t\tjdbcValueBindings,\n\t\t\t\t\tnull,","sourceCodeStart":347,"sourceCodeEnd":383,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/persister/collection/BasicCollectionPersister.java#L347-L383","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nOrder order = new Order(); // transient, no id, no cascade\nList<OrderLine> lines = otherOrder.getLines(); // detached collection\norder.setLines(lines);\nem.persist(order); // em.persist(order) only -> flush writes collection rows with null key\n\n// after\n@OneToMany(mappedBy = \"order\", cascade = CascadeType.ALL)\nprivate List<OrderLine> lines = new ArrayList<>();\n// copy elements, own them, cascade persists owner before rows\nOrder order = new Order();\notherOrder.getLines().forEach(l -> order.addLine(l));\nem.persist(order);","handlingStrategy":"try-catch","validationCode":"// Before flush: the owner must be persisted so the collection FK resolves non-null\nif (owner.getId() == null) {\n  session.persist(owner); // or ensure cascade = PERSIST/ALL on the collection\n}\n// avoid reusing detached PersistentCollection instances across owners\nif (lines instanceof org.hibernate.collection.spi.PersistentCollection<?> pc\n    && pc.getOwner() != null && pc.getOwner() != owner) {\n  owner.setLines(new ArrayList<>(lines)); // fresh collection owned by 'owner'\n}","typeGuard":null,"tryCatchPattern":"try {\n  session.persist(owner);\n  session.flush();\n} catch (IllegalArgumentException e) {\n  if (e.getMessage() != null && e.getMessage().startsWith(\"null key for collection\")) {\n    // collection FK was null at row-insert time: save/attach the owner (cascade), fix join-column mapping, then retry\n    log.warn(\"Null collection key for {} — persisting owner and retrying\", e.getMessage());\n    throw e; // after fixing owner state, a new flush attempt is safe\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["hibernate","flush","foreign-key","collection","cascade"],"backgroundTag":"null-foreign-key","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}