{"record":{"id":"236afe3877f97a62","repo":"hibernate/hibernate-orm","slug":"instance-of-s-references-an-unsaved-transient-i","errorCode":null,"errorMessage":"Instance of '%s' references an unsaved transient instance of '%s' (persist the transient instance before flushing)","messagePattern":"Instance of '(.+?)' references an unsaved transient instance of '(.+?)' \\(persist the transient instance before flushing\\)","errorType":"exception","errorClass":"TransientPropertyValueException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/action/queue/internal/decompose/Decomposer.java","lineNumber":478,"sourceCode":"\t\t\treturn;\n\t\t}\n\n\t\t// Get first unresolved insert for error reporting\n\t\tfinal var firstEntry = unresolvedInserts.entrySet().iterator().next();\n\t\tfinal AbstractEntityInsertAction firstInsert = firstEntry.getKey();\n\t\tfinal NonNullableTransientDependencies dependencies = firstEntry.getValue();\n\n\t\t// Get first transient dependency for error message\n\t\tfinal Object firstTransient = dependencies.getNonNullableTransientEntities().iterator().next();\n\t\tfinal String firstPropertyPath = dependencies.getNonNullableTransientPropertyPaths(firstTransient).iterator().next();\n\n\t\tfinal String entityName = firstInsert.getEntityName();\n\t\tfinal String transientEntityName = session.guessEntityName(firstTransient);\n\n\t\t// Log all unresolved dependencies for debugging\n\t\tlogUnresolvedDependencies();\n\n\t\tthrow new TransientPropertyValueException(\n\t\t\t\t\"Instance of '\" + entityName +\n\t\t\t\t\t\t\"' references an unsaved transient instance of '\" + transientEntityName +\n\t\t\t\t\t\t\"' (persist the transient instance before flushing)\",\n\t\t\t\ttransientEntityName,\n\t\t\t\tentityName,\n\t\t\t\tfirstPropertyPath\n\t\t);\n\t}\n\n\t/// Log details about all unresolved dependencies for debugging purposes.\n\tprivate void logUnresolvedDependencies() {\n\t\t// Log via ActionLogger or similar when available\n\t\t// For now, just track the data for error reporting\n\t\t// The error message already includes the first unresolved dependency\n\t}\n\n\t/// Clear all tracked unresolved inserts. Used for cleanup.\n\tpublic void clear() {","sourceCodeStart":460,"sourceCodeEnd":496,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/action/queue/internal/decompose/Decomposer.java#L460-L496","documentation":"The graph-based flush planner tracks inserts whose non-nullable FK targets are still transient (Decomposer.trackUnresolvedInsert). If the dependencies are never satisfied by the end of planning, Decomposer throws TransientPropertyValueException (\"Instance of 'A' references an unsaved transient instance of 'B' (persist the transient instance before flushing)\", Decomposer.java:478). It is the graph-queue equivalent of the classic unresolved-insert failure: a mandatory association points at an unsaved entity and nothing cascades or persists it.","triggerScenarios":"Setting a non-nullable @ManyToOne to a brand-new entity without CascadeType.PERSIST and flushing under the graph queue (default in 8.x); IDENTITY inserts deferred via hibernate.flush.queue.graph.defer_identity_inserts=true whose transient FK dependencies are never resolved; object graphs assembled in the wrong direction (only the many-side persisted).","commonSituations":"Same as classic TransientPropertyValueException: missing cascade after mapping refactor; builders/tests persisting only the aggregate root while a required child reference is new; switching an association from nullable to mandatory without adding cascade; enabling deferred identity inserts on 8.x exposing previously hidden ordering gaps.","solutions":["Add CascadeType.PERSIST (typically {PERSIST, MERGE}) to the association that references the transient instance","Or persist the referenced entity explicitly before flush: session.persist(child) then flush","If the reference is optional at flush time, make the column/property nullable and populate it in a later step","If dependencies should be resolvable and are not, verify cascade reaches the instance through the exact path you traverse (cascades only follow mapped associations)"],"exampleFix":"// before - required tag never persisted; graph flush throws TransientPropertyValueException\nArticle a = new Article();\na.setTag(new Tag(\"java\"));            // @ManyToOne(optional=false), no cascade\nem.persist(a);\nem.flush();\n\n// after - cascade the persist through the association\n@ManyToOne(optional = false, cascade = {CascadeType.PERSIST, CascadeType.MERGE})\nprivate Tag tag;","handlingStrategy":"validation","validationCode":"// validate required references before flushing the graph\nif (article.getTag() != null && !em.contains(article.getTag())) {\n    em.persist(article.getTag()); // satisfies the non-null FK dependency\n}\nem.persist(article);\nem.flush();","typeGuard":"static boolean isTransient(Object entity, EntityManager em) {\n    return entity != null && !em.contains(entity);\n}","tryCatchPattern":"try {\n    em.flush();\n} catch (TransientPropertyValueException e) {\n    // property path tells you exactly which association was unsaved\n    // fix cascade/persist, then rerun the use case in a fresh transaction\n}","preventionTips":["Cascade {PERSIST, MERGE} on required associations created in the same unit of work","If enabling hibernate.flush.queue.graph.defer_identity_inserts=true, test graphs that mix IDENTITY ids and required FKs","Build aggregates through factory methods that link children to a persisted parent only"],"tags":["transient-instance","cascade","flush","graph-queue","hibernate"],"backgroundTag":"unsaved-transient-instance","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}