{"record":{"id":"30e35e2d7d758cf6","repo":"hibernate/hibernate-orm","slug":"instance-of-entityname-references-an-uns","errorCode":null,"errorMessage":"Instance of '\" + entityName + \"' references an unsaved transient instance of '\" + transientEntityName + \"' (persist the transient instance)","messagePattern":"Instance of '\" \\+ entityName \\+ \"' references an unsaved transient instance of '\" \\+ transientEntityName \\+ \"' \\(persist the transient instance\\)","errorType":"exception","errorClass":"TransientPropertyValueException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/action/internal/UnresolvedEntityInsertActions.java","lineNumber":117,"sourceCode":"\tpublic void checkNoUnresolvedActionsAfterOperation() throws PropertyValueException {\n\t\tif ( isEmpty() ) {\n\t\t\tACTION_LOGGER.noEntityInsertActionsHaveNonNullableTransientDependencies();\n\t\t}\n\t\telse {\n\t\t\tfinal var firstDependentAction = dependenciesByAction.keySet().iterator().next();\n\n\t\t\tlogCannotResolveNonNullableTransientDependencies( firstDependentAction.getSession() );\n\n\t\t\tfinal var nonNullableTransientDependencies = dependenciesByAction.get( firstDependentAction );\n\t\t\tfinal Object firstTransientDependency =\n\t\t\t\t\tnonNullableTransientDependencies.getNonNullableTransientEntities().iterator().next();\n\t\t\tfinal String firstPropertyPath =\n\t\t\t\t\tnonNullableTransientDependencies.getNonNullableTransientPropertyPaths( firstTransientDependency )\n\t\t\t\t\t\t\t.iterator().next();\n\t\t\tfinal String entityName = firstDependentAction.getEntityName();\n\t\t\tfinal String transientEntityName =\n\t\t\t\t\tfirstDependentAction.getSession().guessEntityName( firstTransientDependency );\n\t\t\tthrow new TransientPropertyValueException(\n\t\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)\",\n\t\t\t\t\ttransientEntityName,\n\t\t\t\t\tentityName,\n\t\t\t\t\tfirstPropertyPath\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate void logCannotResolveNonNullableTransientDependencies(@Nonnull SharedSessionContractImplementor session) {\n\t\tfor ( var entry : dependentActionsByTransientEntity.entrySet() ) {\n\t\t\tfinal Object transientEntity = entry.getKey();\n\t\t\tfinal String transientEntityName = session.guessEntityName( transientEntity );\n\t\t\tfinal Object transientEntityId =\n\t\t\t\t\tsession.getFactory().getMappingMetamodel()\n\t\t\t\t\t\t\t.getEntityDescriptor( transientEntityName )\n\t\t\t\t\t\t\t.getIdentifier( transientEntity, session );","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/action/internal/UnresolvedEntityInsertActions.java#L99-L135","documentation":"At flush time, UnresolvedEntityInsertActions (UnresolvedEntityInsertActions.java:117) tracks inserts whose non-nullable foreign-key targets are still transient (unsaved). If those dependencies cannot be resolved by the end of flush, Hibernate throws TransientPropertyValueException (\"Instance of 'A' references an unsaved transient instance of 'B' (persist the transient instance)\"). It means you linked a new entity to another new entity over a mandatory association but never cascaded or explicitly persisted the target, so the INSERT cannot be ordered or executed.","triggerScenarios":"parent.setChild(newChild()) where @ManyToOne is non-nullable and has no CascadeType.PERSIST, then flush/commit; building an object graph bottom-up and only persisting one end; using save() on the parent while the child is reachable only through a non-cascaded association.","commonSituations":"Entities generated by mapping tools/Lombok builders that drop cascade settings; refactoring that adds a new required @ManyToOne without cascade; test fixtures creating graphs but persisting only the root; switching from session.merge() (which cascades) to session.persist() paths.","solutions":["Add cascade to the association that leads to the transient instance: @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) or @OneToMany(cascade = ALL) on the inverse side you actually traverse","Or persist the referenced instance explicitly before flush: session.persist(child); session.persist(parent);","If the association may legitimately be absent at flush time, make it nullable (nullable = false -> true / @JoinColumn nullable) and set it later","Check for accidental references to stray transient objects (e.g. builder defaults) that you never intended to link - remove the assignment"],"exampleFix":"// before - transient child never persisted, flush throws TransientPropertyValueException\n@Entity class Order {\n    @ManyToOne(optional = false) // no cascade\n    private Customer customer;\n}\norder.setCustomer(new Customer(...));\nsession.persist(order);\nsession.flush();\n\n// after - cascade persists the transient instance\n@ManyToOne(optional = false, cascade = {CascadeType.PERSIST, CascadeType.MERGE})\nprivate Customer customer;","handlingStrategy":"validation","validationCode":"// before flush, check that every required association target is persisted or cascaded\nstatic void verifyNoTransientRefs(Session session, Object parent, Object... refs) {\n    for (Object ref : refs) {\n        if (ref != null && !session.contains(ref)) {\n            session.persist(ref); // or throw: \"persist child before parent\"\n        }\n    }\n    session.persist(parent);\n}","typeGuard":"static boolean isTransient(Session session, Object entity) {\n    return entity != null && !session.contains(entity) && session.getIdentifier(entity) == null;\n}","tryCatchPattern":"try {\n    session.flush();\n} catch (TransientPropertyValueException e) {\n    // message names both entities and the property path:\n    // persist e.getPropertyName() target then retry the unit of work in a NEW transaction\n    log.warn(\"unresolved transient ref on {}.{} - add cascade or persist first\",\n             e.getPropertyName());\n}","preventionTips":["Default to cascade = {PERSIST, MERGE} on required @ManyToOne associations that are always created together","Persist aggregate roots and let cascades reach the children; avoid persisting fragments of a graph","Watch for TransientPropertyValueException in integration tests - it is cheaper to fix there than in production"],"tags":["transient-instance","cascade","associations","flush","hibernate"],"backgroundTag":"unsaved-transient-instance","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}