{"record":{"id":"ac548b7099fc960c","repo":"hibernate/hibernate-orm","slug":"multiple-representations-of-the-same-entity","errorCode":null,"errorMessage":"Multiple representations of the same entity ","messagePattern":"Multiple representations of the same entity ","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/event/internal/EntityCopyNotAllowedObserver.java","lineNumber":38,"sourceCode":"\tpublic static final String SHORT_NAME = \"disallow\";\n\tprivate static final EntityCopyNotAllowedObserver INSTANCE = new EntityCopyNotAllowedObserver();\n\t//This implementation of EntityCopyObserver is stateless, so no need to create multiple copies:\n\tpublic static final EntityCopyObserverFactory FACTORY_OF_SELF = () -> INSTANCE;\n\n\tprivate EntityCopyNotAllowedObserver() {\n\t\t//Not to be constructed; use INSTANCE.\n\t}\n\n\t@Override\n\tpublic void entityCopyDetected(\n\t\t\t@Nonnull Object managedEntity,\n\t\t\t@Nonnull Object mergeEntity1,\n\t\t\t@Nonnull Object mergeEntity2,\n\t\t\t@Nonnull EventSource session) {\n\t\tif ( mergeEntity1 == managedEntity && mergeEntity2 == managedEntity) {\n\t\t\tthrow new AssertionFailure( \"entity1 and entity2 are the same as managedEntity; must be different\" );\n\t\t}\n\t\tthrow new IllegalStateException( \"Multiple representations of the same entity \"\n\t\t\t\t+ infoString( session.getEntityName( managedEntity ), session.getIdentifier( managedEntity ) )\n\t\t\t\t+ \" are being merged: \" + managedOrDetachedEntityString( managedEntity, mergeEntity1 )\n\t\t\t\t+ \"; \" + managedOrDetachedEntityString( managedEntity, mergeEntity2 ) );\n\t}\n\n\tprivate @Nonnull String managedOrDetachedEntityString(@Nonnull Object managedEntity, @Nonnull Object entity ) {\n\t\treturn new StringBuilder()\n\t\t\t\t.append( entity == managedEntity ? \"Managed\" : \"Detached\" )\n\t\t\t\t.append( \" [\" )\n\t\t\t\t.append( entity )\n\t\t\t\t.append( ']' )\n\t\t\t\t.toString();\n\t}\n\n\tpublic void clear() {\n\t\t// Nothing to do\n\t}\n","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/event/internal/EntityCopyNotAllowedObserver.java#L20-L56","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nOrder order = orderFromJson; // holds customer and referrer as two distinct Customer objects with id=7\nsession.merge(order); // IllegalStateException: Multiple representations of the same entity\n\n// after\n// canonicalize duplicates by id before merging\nMap<Long, Customer> byId = new LinkedHashMap<>();\norder.getCustomerRefs().forEach(c -> byId.merge(c.getId(), c, (a, b) -> a));\norder.setCustomer(byId.get(order.getCustomer().getId()));\nsession.merge(order);","handlingStrategy":"validation","validationCode":"Map<List<Object>, Object> seen = new IdentityHashMap<>(); // per (class,id)\n// canonicalize before merge\nBiFunction<Object, Object, Object> canonical = (existing, incoming) -> existing;\n// simple by-id map for one type:\nMap<Long, Customer> byId = new LinkedHashMap<>();\nfor (Customer c : Arrays.asList(order.getCustomer(), order.getReferrer())) {\n    byId.merge(c.getId(), c, (a, b) -> { copyState(b, a); return a; });\n}\norder.setCustomer(byId.get(order.getCustomer().getId()));\norder.setReferrer(byId.get(order.getReferrer().getId()));\nsession.merge(order);","typeGuard":"boolean hasDuplicateIds(Collection<Customer> customers) {\n    return customers.stream().map(Customer::getId).distinct().count() != customers.size();\n}","tryCatchPattern":"try {\n    return session.merge(order);\n} catch (IllegalStateException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"Multiple representations\")) {\n        // graph contains two detached copies of one row: canonicalize and retry\n        return session.merge(canonicalizeById(order));\n    }\n    throw e;\n}","preventionTips":["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"],"tags":["hibernate","merge","entity-copy","object-graph","cascade","orm"],"backgroundTag":"duplicate-entity-merge","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}