{"record":{"id":"8c6d63791f5f91b6","repo":"hibernate/hibernate-orm","slug":"query-result-contains-conflicting-version-of-entit","errorCode":null,"errorMessage":"Query result contains conflicting version of entity already held in persistence context","messagePattern":"Query result contains conflicting version of entity already held in persistence context","errorType":"exception","errorClass":"StaleObjectStateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java","lineNumber":1479,"sourceCode":"\t * Check the version of the object in the {@code RowProcessingState} against\n\t * the object version in the session cache, throwing an exception\n\t * if the version numbers are different\n\t */\n\tprivate void checkVersion(\n\t\t\tEntityInitializerData data,\n\t\t\tEntityEntry entry,\n\t\t\tRowProcessingState rowProcessingState) {\n\t\tfinal Object version = entry.getVersion();\n\t\tif ( version != null ) {\n\t\t\t// null version means the object is in the process of being loaded somewhere else in the ResultSet\n\t\t\tfinal Object currentVersion = versionAssembler.assemble( rowProcessingState );\n\t\t\tif ( !data.concreteDescriptor.getVersionType().isEqual( version, currentVersion ) ) {\n\t\t\t\tfinal String entityName = data.concreteDescriptor.getEntityName();\n\t\t\t\tfinal var statistics = rowProcessingState.getSession().getFactory().getStatistics();\n\t\t\t\tif ( statistics.isStatisticsEnabled() ) {\n\t\t\t\t\tstatistics.optimisticFailure( entityName );\n\t\t\t\t}\n\t\t\t\tthrow new StaleObjectStateException( entityName, entry.getId(),\n\t\t\t\t\t\t\"Query result contains conflicting version of entity already held in persistence context\" );\n\t\t\t}\n\t\t}\n\n\t}\n\n\t/**\n\t * Used by Hibernate Reactive\n\t */\n\tprotected Object resolveEntityInstance2(EntityInitializerData data) {\n\t\tif ( data.entityHolder.getEntityInitializer() == this ) {\n\t\t\tassert data.entityHolder.getEntity() == null;\n\t\t\treturn resolveEntityInstance( data );\n\t\t}\n\t\telse {\n\t\t\t// the entity is already being loaded elsewhere\n\t\t\treturn data.entityHolder.getEntity();\n\t\t}","sourceCodeStart":1461,"sourceCodeEnd":1497,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java#L1461-L1497","documentation":"When a query result provides a row for an entity already managed in the persistence context (first-level cache), EntityInitializerImpl checks the @Version column against the version held in the entity's EntityEntry. If they are not equal, it records an optimistic failure in statistics and throws StaleObjectStateException with 'Query result contains conflicting version of entity already held in persistence context'. Hibernate refuses to hand back an instance whose state contradicts what the session already believes, because two versions of the same entity would coexist in one unit of work.","triggerScenarios":"Session loads entity E at version 1; another transaction (or a direct JDBC/bulk update) commits version 2; the same session then runs any query returning E's row - the version mismatch is detected during result processing.","commonSituations":"Long-lived sessions (desktop apps, stateful web flows) spanning external updates; bulk JPQL/native updates committed by other instances of the application; background jobs mutating rows while a user's session stays open; missing evict/detach of entities after data changes outside the session.","solutions":["Refresh the instance from the database (em.refresh(entity)) before re-querying, so the session's snapshot matches the row.","Keep sessions short (one unit of work per request); evict or clear the session when an external update may have occurred.","Acquire an appropriate lock when loading (LockModeType.OPTIMISTIC / PESSIMISTIC_WRITE) so concurrent updates cannot slip in between load and re-query.","For bulk external updates that bypass the session, evict the affected entities (or clear caches) immediately after the update."],"exampleFix":"// before: stale managed instance conflicts with the fresh row\nOrder o = em.find(Order.class, 1L);       // version 1\notherService.updateOrderToVersion2();      // commits version 2\nem.createQuery(\"select o from Order o\", Order.class).getResultList(); // StaleObjectStateException\n\n// after: refresh or evict before re-querying\nOrder o = em.find(Order.class, 1L);\notherService.updateOrderToVersion2();\nem.refresh(o);                              // re-read version 2\nem.createQuery(\"select o from Order o\", Order.class).getResultList();","handlingStrategy":"retry","validationCode":"// Before re-querying in a long session, compare the managed version to the DB\nObject managed = em.find(Order.class, id);\nObject dbVersion = em.createQuery(\n    \"select o.version from Order o where o.id = :id\", Object.class)\n    .setParameter(\"id\", id).getSingleResult();\nif (managed != null && !Objects.equals(((Versioned) managed).getVersion(), dbVersion)) {\n    em.refresh(managed); // align the persistence context before the conflicting query\n}","typeGuard":null,"tryCatchPattern":"for (int attempt = 0; attempt < 2; attempt++) {\n    try {\n        return em.createQuery(\"select o from Order o\", Order.class).getResultList();\n    } catch (StaleObjectStateException e) {\n        em.clear(); // drop stale persistence-context state, then retry once\n    }\n}\nthrow new IllegalStateException(\"order query keeps conflicting with persistence context\");","preventionTips":["Keep sessions short - one unit of work per request; never cache EntityManager across requests.","Refresh (em.refresh) instances you keep holding after external updates.","After bulk updates, evict/clear affected entities from still-open sessions.","Load rows you intend to modify with an explicit lock (OPTIMISTIC/PESSIMISTIC) so versions cannot diverge silently."],"tags":["hibernate","orm","optimistic-locking","versioning","persistence-context","stale-state"],"backgroundTag":"optimistic-lock-version-conflict","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}