{"record":{"id":"efdcf444d094d145","repo":"hibernate/hibernate-orm","slug":"s-for-entity-s-s","errorCode":null,"errorMessage":"%s for entity %s#%s","messagePattern":"(.+?) for entity (.+?)#(.+?)","errorType":"exception","errorClass":"StaleObjectStateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/action/queue/spi/bind/Checkers.java","lineNumber":48,"sourceCode":"\t\t\tTableDescriptor mutatingTable,\n\t\t\tObject id,\n\t\t\tString sqlString,\n\t\t\tSessionFactoryImplementor sessionFactory) {\n\t\ttry {\n\t\t\texpectation.verifyOutcome(\n\t\t\t\t\taffectedRowCount,\n\t\t\t\t\tnull,\n\t\t\t\t\tbatchPosition,\n\t\t\t\t\tsqlString\n\t\t\t);\n\t\t}\n\t\tcatch (StaleStateException e) {\n\t\t\tif ( !mutatingTable.isOptional() && affectedRowCount == 0 ) {\n\t\t\t\tfinal StatisticsImplementor statistics = sessionFactory.getStatistics();\n\t\t\t\tif ( statistics.isStatisticsEnabled() ) {\n\t\t\t\t\tstatistics.optimisticFailure( mutationTarget.getNavigableRole().getFullPath() );\n\t\t\t\t}\n\t\t\t\tthrow new StaleObjectStateException( mutationTarget.getNavigableRole().getFullPath(), id, e );\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t\tcatch (TooManyRowsAffectedException e) {\n\t\t\tthrow new HibernateException(\n\t\t\t\t\tString.format(\n\t\t\t\t\t\t\tLocale.ROOT,\n\t\t\t\t\t\t\t\"Duplicate identifier in table (%s) - %s#%s\",\n\t\t\t\t\t\t\tmutatingTable.name(),\n\t\t\t\t\t\t\tmutationTarget.getNavigableRole().getFullPath(),\n\t\t\t\t\t\t\tid\n\t\t\t\t\t)\n\t\t\t);\n\t\t}\n\t\tcatch (Throwable t) {\n\t\t\treturn false;\n\t\t}\n","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/action/queue/spi/bind/Checkers.java#L30-L66","documentation":"Optimistic-locking failure during mutation of an identified table: Expectation.verifyOutcome raised StaleStateException, and because the mutating table is not optional a 0-row UPDATE/DELETE is fatal, so Checkers rethrows StaleObjectStateException (message '<reason> for entity <entityName>#<id>') carrying the navigable path and id. It means the row was updated or deleted by another transaction, or the id/unsaved-value mapping made Hibernate update a row that never existed.","triggerScenarios":"Concurrent transactions modifying the same @Version row; the row deleted outside the session (another job, manual SQL) before flush; an entity saved with an assigned id but never inserted, so the UPDATE matches zero rows.","commonSituations":"Two users editing the same record (lost-update detection working as designed); batch jobs deleting rows out from under an open session; assigned-id generators plus wrong unsaved-value configuration; secondary table rows absent while mapped as non-optional.","solutions":["Treat it as a business conflict: catch StaleObjectStateException, roll back, reload the latest state and reapply the user's changes (or ask the user to merge).","If the row was legitimately deleted elsewhere, handle that event instead of retrying.","If Hibernate should never have updated, audit the id strategy: is the entity new but carrying an id (wrong unsaved-value / persist vs merge misuse)?","If rows may legitimately be absent from this table, map it optional (e.g. @Table(optional = true) semantics) so a 0-row result returns false instead of throwing."],"exampleFix":"// before\nsession.merge(order); // throws StaleObjectStateException on conflict\n// after - detect, roll back, reapply on fresh state\ntry {\n    session.merge(order);\n    tx.commit();\n}\ncatch (StaleObjectStateException e) {\n    tx.rollback();\n    Order fresh = newSession.get(Order.class, order.getId());\n    fresh.setQty(order.getQty()); // reapply and commit\n}","handlingStrategy":"retry","validationCode":"// optional pre-check: confirm the row still exists before editing in a long conversation\nboolean exists = session.find(Order.class, orderId) != null;","typeGuard":null,"tryCatchPattern":"for (int attempt = 0; attempt < 3; attempt++) {\n    try {\n        tx = em.getTransaction(); tx.begin();\n        Order o = em.find(Order.class, orderId);\n        o.setQty(newQty);\n        tx.commit();\n        break;\n    }\n    catch (StaleObjectStateException e) {\n        if (tx.isActive()) tx.rollback();\n        // reload latest state and reapply, or give up and report the conflict\n    }\n}","preventionTips":["Add @Version to shared entities so conflicts surface as this exception instead of silent lost updates","Keep transactions short; do not hold entities across user think-time","Use merge with fresh state after rollback instead of reusing the stale instance"],"tags":["hibernate","optimistic-locking","concurrency","stale-state","flush"],"backgroundTag":"optimistic-lock-conflict","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}