hibernate/hibernate-orm · error · IllegalStateException

Multiple representations of the same entity

Error message

Multiple representations of the same entity 

What it means

IllegalStateException from EntityCopyNotAllowedObserver.entityCopyDetected — the default observer when hibernate.event.merge.entity_copy_observer is 'disallow' (the default). During session.merge(graph), if two distinct detached Java instances with the same identifier for the same entity are merged onto one managed instance, the observer is invoked with managedEntity, mergeEntity1 and mergeEntity2; unless one of them is the managed instance itself, it throws. The message prints the entity name, identifier, and whether each conflicting copy is Managed or Detached.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/event/internal/EntityCopyNotAllowedObserver.java:38

	public static final String SHORT_NAME = "disallow";
	private static final EntityCopyNotAllowedObserver INSTANCE = new EntityCopyNotAllowedObserver();
	//This implementation of EntityCopyObserver is stateless, so no need to create multiple copies:
	public static final EntityCopyObserverFactory FACTORY_OF_SELF = () -> INSTANCE;

	private EntityCopyNotAllowedObserver() {
		//Not to be constructed; use INSTANCE.
	}

	@Override
	public void entityCopyDetected(
			@Nonnull Object managedEntity,
			@Nonnull Object mergeEntity1,
			@Nonnull Object mergeEntity2,
			@Nonnull EventSource session) {
		if ( mergeEntity1 == managedEntity && mergeEntity2 == managedEntity) {
			throw new AssertionFailure( "entity1 and entity2 are the same as managedEntity; must be different" );
		}
		throw new IllegalStateException( "Multiple representations of the same entity "
				+ infoString( session.getEntityName( managedEntity ), session.getIdentifier( managedEntity ) )
				+ " are being merged: " + managedOrDetachedEntityString( managedEntity, mergeEntity1 )
				+ "; " + managedOrDetachedEntityString( managedEntity, mergeEntity2 ) );
	}

	private @Nonnull String managedOrDetachedEntityString(@Nonnull Object managedEntity, @Nonnull Object entity ) {
		return new StringBuilder()
				.append( entity == managedEntity ? "Managed" : "Detached" )
				.append( " [" )
				.append( entity )
				.append( ']' )
				.toString();
	}

	public void clear() {
		// Nothing to do
	}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Canonicalize the graph before merge: keep exactly one instance per (entity type, id) — build an identity map keyed by id, merge duplicate properties, and rewire all references to the canonical instance
  2. Change hibernate.event.merge.entity_copy_observer=allow (Hibernate picks a state per copy, last-merge-wins per property) or =log to diagnose which associations carry the duplicates before allowing
  3. Re-load the canonical managed instances via session.find/entityReference by id and copy incoming state onto them instead of merging a duplicate-laden graph

Example fix

// before
Order order = orderFromJson; // holds customer and referrer as two distinct Customer objects with id=7
session.merge(order); // IllegalStateException: Multiple representations of the same entity

// after
// canonicalize duplicates by id before merging
Map<Long, Customer> byId = new LinkedHashMap<>();
order.getCustomerRefs().forEach(c -> byId.merge(c.getId(), c, (a, b) -> a));
order.setCustomer(byId.get(order.getCustomer().getId()));
session.merge(order);
Defensive patterns

Strategy: validation

Validate before calling

Map<List<Object>, Object> seen = new IdentityHashMap<>(); // per (class,id)
// canonicalize before merge
BiFunction<Object, Object, Object> canonical = (existing, incoming) -> existing;
// simple by-id map for one type:
Map<Long, Customer> byId = new LinkedHashMap<>();
for (Customer c : Arrays.asList(order.getCustomer(), order.getReferrer())) {
    byId.merge(c.getId(), c, (a, b) -> { copyState(b, a); return a; });
}
order.setCustomer(byId.get(order.getCustomer().getId()));
order.setReferrer(byId.get(order.getReferrer().getId()));
session.merge(order);

Type guard

boolean hasDuplicateIds(Collection<Customer> customers) {
    return customers.stream().map(Customer::getId).distinct().count() != customers.size();
}

Try / catch

try {
    return session.merge(order);
} catch (IllegalStateException e) {
    if (e.getMessage() != null && e.getMessage().contains("Multiple representations")) {
        // graph contains two detached copies of one row: canonicalize and retry
        return session.merge(canonicalizeById(order));
    }
    throw e;
}

Prevention

When it happens

Trigger: session.merge(graph) where the object graph contains two different Java objects for the same database row — e.g. an order referencing two separate detached instances of the same Customer (same id) through different associations, both reached by cascade merge in one flush graph. Also merging two separately-loaded copies of the same entity into one graph, then merging the graph.

Common situations: Graphs deserialized from JSON (REST payload) where the same nested record appears twice as independent objects; DTO-to-entity mapping (MapStruct/Dozer/cloning) producing duplicate instances per id; loading the same row via two queries in one request and linking both results into a parent that is later merged; bi-directional cascades (CascadeType.MERGE on both sides) causing the same copy to be visited from two paths with different instance states.

Related errors


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