hibernate/hibernate-orm · error · HibernateException

Collection with orphan orphan delete enabled has modifier ow

Error message

Collection with orphan orphan delete enabled has modifier owner: {}

What it means

The second guard in checkOnChangedOwner: when a collection was loaded under one persister (role) but at flush its current persister is a DIFFERENT non-null role and the loaded side has orphanRemoval, Hibernate throws. In plain terms, a managed collection instance with orphan-delete semantics ended up owned by a different association/owner - Hibernate cannot decide orphan deletion across roles, so the owner change is rejected (note the message text is garbled in the source: 'orphan orphan delete ... modifier owner').

Source

Thrown at hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java:312

	}

	private static void checkOnChangedOwner(PersistentCollection<?> collection, CollectionEntry collectionEntry, CollectionPersister loadedPersister, CollectionPersister currentPersister) {
		final boolean immutableDereferenced =
				collectionEntry.isReadOnly()
				&& loadedPersister != null
				&& !isOwnerDeletedOrGone( collection );
		if ( immutableDereferenced ) {
			throw new HibernateException( "Immutable collection dereferenced by owner: "
						+ collectionInfoString( loadedPersister.getRole(), collectionEntry.getLoadedKey() ) );
		}


		final boolean orphanDeleteAndRoleChanged =
				loadedPersister != null
				&& currentPersister != null
				&& loadedPersister.hasOrphanDelete();
		if ( orphanDeleteAndRoleChanged ) {
			throw new HibernateException(
					"Collection with orphan orphan delete enabled has modifier owner: "
					+ collectionInfoString( loadedPersister.getRole(), collectionEntry.getLoadedKey() ) );
		}
	}

	private static boolean isOwnerDeletedOrGone(PersistentCollection<?> collection) {
		final Object owner = collection.getOwner();
		assert owner != null;
		final var session = collection.getSession();
		assert session != null;
		final var entry = session.getPersistenceContextInternal().getEntry( owner );
		return entry != null && entry.getStatus().isDeletedOrGone();
	}

	/**
	 * Check if the key changed.
	 * Excludes marking key changed when the loaded key is a {@code DelayedPostInsertIdentifier}.
	 */

View on GitHub (pinned to fad1729dce)

Solutions

  1. Never move a managed collection instance between associations - create a new collection and copy elements.
  2. Re-model re-parenting as explicit element moves: remove child from A's collection, add to B's collection (each association keeps its own instance).
  3. Remove orphanRemoval=true if wholesale reassignment between owners is a legitimate operation in your domain (then delete orphans manually).
  4. Find the offending assignment via the role name in the message (collectionInfoString includes role and key).

Example fix

// before - managed collection with orphanRemoval moved to another owner
studentB.setScores(studentA.getScores());
// after - new instance per owner, elements moved explicitly
studentB.setScores(new ArrayList<>(studentA.getScores()));
studentA.setScores(new ArrayList<>());
Defensive patterns

Strategy: validation

Validate before calling

// Guard: block owner changes for managed orphanRemoval collections
static <T> void moveToOtherOwner(List<T> from, List<T> to, T item) {
    from.remove(item);
    if (to == from) throw new IllegalArgumentException("same collection instance reused across owners");
    to.add(item);   // each association keeps its own collection instance
}

Type guard

static boolean isPersistentCollection(Object c) {
    return c instanceof org.hibernate.collection.spi.PersistentCollection;
}

Try / catch

catch (HibernateException e) {
    if (e.getMessage() != null && e.getMessage().contains("orphan delete enabled has modifier owner")) {
        // role+key in message; replace the cross-owner assignment with element moves
        throw new MappingMisuseException(e.getMessage(), e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Reassigning a Hibernate-managed collection instance from one entity's orphanRemoval=true association to another entity/property with a different role: b.setScores(a.getScores()) where Scores has orphanRemoval; re-parenting logic that moves collection references between associations; merge of graphs where collections migrate between owners.

Common situations: Refactoring that moves a collection property from one entity to another while old data paths still copy the reference; batch code 'recycling' collections between rows; copy/paste wiring of bidirectional relations on orphanRemoval associations.

Related errors


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