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
- 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
- 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
- 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 deserializing JSON into entities, deduplicate nested objects by id before merge
- Set hibernate.event.merge.entity_copy_observer=log in test environments to surface duplicate copies early
- Prefer loading managed instances by id and copying incoming state over merging raw graphs
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
- Row was already updated or deleted by another transaction
- Dialect does not support constraint name in conflict clause
- Insert conflict 'do update' clause with constraint name is n
- Instance of '" + entityName + "' references an unsaved trans
- Instance of '%s' references an unsaved transient instance of
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/ac548b7099fc960c.
Report an issue: GitHub.