{"record":{"id":"dccf58ba0373025f","repo":"hibernate/hibernate-orm","slug":"null-key-for-collection-s-dccf58","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/SingleRowInsertBindPlan.java","lineNumber":73,"sourceCode":"\t\t\tSharedSessionContractImplementor session) {\n\t\treturn CollectionUniqueKeyValueExtractor.extractValues(\n\t\t\t\tpersister,\n\t\t\t\tcollection,\n\t\t\t\tkey,\n\t\t\t\tentry,\n\t\t\t\tentryIndex,\n\t\t\t\tconstraint,\n\t\t\t\tsession\n\t\t);\n\t}\n\n\t@Override\n\tpublic void bindValues(\n\t\t\tJdbcValueBindings jdbcValueBindings,\n\t\t\tFlushOperation flushOperation,\n\t\t\tSharedSessionContractImplementor session) {\n\t\tif ( key == null ) {\n\t\t\tthrow new IllegalArgumentException( \"null key for collection: \" + persister.getNavigableRole().getFullPath() );\n\t\t}\n\n\t\tvalues.applyValues( collection, key, entry, entryIndex, session, jdbcValueBindings );\n\t}\n}\n","sourceCodeStart":55,"sourceCodeEnd":79,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/action/queue/internal/decompose/collection/SingleRowInsertBindPlan.java#L55-L79","documentation":"SingleRowInsertBindPlan is the bind plan the graph flush planner uses for inserting one collection row. Its bindValues (SingleRowInsertBindPlan.java:73) guards the owner key first: null key means there is no FK value to bind for the row, so it throws IllegalArgumentException(\"null key for collection: <role>\"). Like the other null-key guards, the collection is being written while its owner's identifier is null.","triggerScenarios":"Adding an element to a collection whose owning entity has no id yet (transient owner without cascade from a saved parent; assigned id not set; null-returning generator) and flushing - the planner creates a SingleRowInsertBindPlan and the key check fails at JDBC binding time.","commonSituations":"One-directional cascades that persist the element but not the owner; new-parent test flows that forget session.persist(parent); code that clears ids for 'clone' operations; generators with edge-case null returns after upgrades.","solutions":["Persist the owner before or together with the element: session.persist(owner) or add cascade on the side you actually traverse","Keep both sides of the bidirectional association in sync and always persist from the cascading root","Ensure assigned ids are always set before persist - validate in @PrePersist or the entity factory","Guard custom IdentifierGenerator.generate() to never return null"],"exampleFix":"// before - only the child persisted; owner has no id; insert plan needs a null FK\nParent p = new Parent();           // transient, no cascade reaching it\np.getChildren().add(child);\nsession.persist(child);\nsession.flush();                   // -> \"null key for collection: Parent.children\"\n\n// after - persist the owner, cascade saves children\n@OneToMany(mappedBy = \"parent\", cascade = CascadeType.ALL)\nprivate List<Child> children = new ArrayList<>();\nsession.persist(p);                // p.getChildren().add(child) beforehand","handlingStrategy":"validation","validationCode":"// persist from the cascading root so the owner key exists when rows bind\n@OneToMany(mappedBy = \"parent\", cascade = CascadeType.ALL)\nList<Child> children = new ArrayList<>();\n\nParent p = new Parent();\np.getChildren().add(child);\nchild.setParent(p);\nsession.persist(p);   // owner id generated; child insert binds a real FK","typeGuard":null,"tryCatchPattern":"try {\n    session.flush();\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"null key for collection\")) {\n        // single-row insert had no owner FK: persist the owner and re-run\n    } else throw e;\n}","preventionTips":["Persist aggregates from the root and let CascadeType.ALL reach children","Keep both sides of bidirectional associations synchronized in helper methods","Reject code paths that persist collection elements without their owner"],"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"}