{"record":{"id":"f5cb3f1c90a02861","repo":"hibernate/hibernate-orm","slug":"null-key-for-collection-s","errorCode":null,"errorMessage":"null key for collection: %s","messagePattern":"null key for collection: (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/action/queue/internal/decompose/collection/BasicCollectionDecomposer.java","lineNumber":1265,"sourceCode":"\t\t\tinsertBuilder.addColumnAssignment( temporalMapping.createStartingValueBinding( startingColumn ) );\n\n\t\t\tfinal var endingColumn = new ColumnReference(\n\t\t\t\t\tinsertBuilder.getMutatingTable(),\n\t\t\t\t\ttemporalMapping.getEndingColumnMapping()\n\t\t\t);\n\t\t\tinsertBuilder.addColumnAssignment( temporalMapping.createNullEndingValueBinding( endingColumn ) );\n\t\t}\n\t}\n\n\tprivate void bindInsertRowValues(\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: \"\n\t\t\t\t\t+ persister.getNavigableRole().getFullPath() );\n\t\t}\n\n\t\tfinal var attributeMapping = persister.getAttributeMapping();\n\t\tattributeMapping.getKeyDescriptor().getKeyPart().decompose(\n\t\t\t\tkey,\n\t\t\t\tjdbcValueBindings::bindAssignment,\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\tjdbcValueBindings::bindAssignment,\n\t\t\t\t\tsession\n\t\t\t);\n\t\t}","sourceCodeStart":1247,"sourceCodeEnd":1283,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/action/queue/internal/decompose/collection/BasicCollectionDecomposer.java#L1247-L1283","documentation":"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.","triggerScenarios":"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.","commonSituations":"@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.","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"],"exampleFix":"// before - owner never persisted, flush of its collection has null key\nUser u = new User();           // no id assigned (manual generator)\nu.getTags().add(tag);\nsession.persist(tag);           // only the element saved\nsession.flush();                // -> \"null key for collection: User.tags\"\n\n// after - persist the owner so the key exists, cascade elements\nsession.persist(u);             // id assigned / owner managed\nu.getTags().add(tag);\nsession.flush();","handlingStrategy":"validation","validationCode":"// ensure the owner has an id before its collection can flush\nif (session.getIdentifier(owner) == null && !session.contains(owner)) {\n    session.persist(owner); // assigns/queues the id and makes the owner managed\n}\nowner.getItems().add(item);","typeGuard":null,"tryCatchPattern":"try {\n    session.flush();\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"null key for collection\")) {\n        // owner id missing: persist the owner, then retry the unit of work\n    } else throw e;\n}","preventionTips":["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"],"tags":["collections","null-key","identifiers","flush","graph-queue","hibernate"],"backgroundTag":"null-collection-key","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}