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

  1. 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
  2. Or persist the referenced instance explicitly before flush: session.persist(child); session.persist(parent);
  3. If the association may legitimately be absent at flush time, make it nullable (nullable = false -> true / @JoinColumn nullable) and set it later
  4. 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

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


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/30e35e2d7d758cf6. Report an issue: GitHub.