hibernate/hibernate-orm · error · TransientPropertyValueException
Instance of '" + entityName + "' references an unsaved trans
Error message
Instance of '" + entityName + "' references an unsaved transient instance of '" + transientEntityName + "' (persist the transient instance)
What it means
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.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/action/internal/UnresolvedEntityInsertActions.java:117
public void checkNoUnresolvedActionsAfterOperation() throws PropertyValueException {
if ( isEmpty() ) {
ACTION_LOGGER.noEntityInsertActionsHaveNonNullableTransientDependencies();
}
else {
final var firstDependentAction = dependenciesByAction.keySet().iterator().next();
logCannotResolveNonNullableTransientDependencies( firstDependentAction.getSession() );
final var nonNullableTransientDependencies = dependenciesByAction.get( firstDependentAction );
final Object firstTransientDependency =
nonNullableTransientDependencies.getNonNullableTransientEntities().iterator().next();
final String firstPropertyPath =
nonNullableTransientDependencies.getNonNullableTransientPropertyPaths( firstTransientDependency )
.iterator().next();
final String entityName = firstDependentAction.getEntityName();
final String transientEntityName =
firstDependentAction.getSession().guessEntityName( firstTransientDependency );
throw new TransientPropertyValueException(
"Instance of '" + entityName
+ "' references an unsaved transient instance of '" + transientEntityName
+ "' (persist the transient instance)",
transientEntityName,
entityName,
firstPropertyPath
);
}
}
private void logCannotResolveNonNullableTransientDependencies(@Nonnull SharedSessionContractImplementor session) {
for ( var entry : dependentActionsByTransientEntity.entrySet() ) {
final Object transientEntity = entry.getKey();
final String transientEntityName = session.guessEntityName( transientEntity );
final Object transientEntityId =
session.getFactory().getMappingMetamodel()
.getEntityDescriptor( transientEntityName )
.getIdentifier( transientEntity, session );View on GitHub (pinned to fad1729dce)
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
Example fix
// before - transient child never persisted, flush throws TransientPropertyValueException
@Entity class Order {
@ManyToOne(optional = false) // no cascade
private Customer customer;
}
order.setCustomer(new Customer(...));
session.persist(order);
session.flush();
// after - cascade persists the transient instance
@ManyToOne(optional = false, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Customer customer; Defensive patterns
Strategy: validation
Validate before calling
// before flush, check that every required association target is persisted or cascaded
static void verifyNoTransientRefs(Session session, Object parent, Object... refs) {
for (Object ref : refs) {
if (ref != null && !session.contains(ref)) {
session.persist(ref); // or throw: "persist child before parent"
}
}
session.persist(parent);
} Type guard
static boolean isTransient(Session session, Object entity) {
return entity != null && !session.contains(entity) && session.getIdentifier(entity) == null;
} Try / catch
try {
session.flush();
} catch (TransientPropertyValueException e) {
// message names both entities and the property path:
// persist e.getPropertyName() target then retry the unit of work in a NEW transaction
log.warn("unresolved transient ref on {}.{} - add cascade or persist first",
e.getPropertyName());
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Instance of '%s' references an unsaved transient instance of
- There are delayed insert actions before operation as cascade
- Flush during cascade is dangerous
- deleted object would be re-saved by cascade (remove deleted
- null key for collection: {}
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/30e35e2d7d758cf6.
Report an issue: GitHub.