{"record":{"id":"02b5498fd484f6b0","repo":"hibernate/hibernate-orm","slug":"cannot-force-version-increment-relative-to-subtype","errorCode":null,"errorMessage":"Cannot force version increment relative to subtype; use the root type","messagePattern":"Cannot force version increment relative to subtype; use the root type","errorType":"exception","errorClass":"HibernateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java","lineNumber":137,"sourceCode":"\t\treturn versionUpdateGroup;\n\t}\n\n\tprotected BatchKey getBatchKey() {\n\t\treturn batchKey;\n\t}\n\n\tpublic final boolean isModifiableEntity(EntityEntry entry) {\n\t\treturn entry == null ? entityPersister().isMutable() : entry.isModifiableEntity();\n\t}\n\n\t@Override\n\tpublic void forceVersionIncrement(\n\t\t\tObject id,\n\t\t\tObject currentVersion,\n\t\t\tObject nextVersion,\n\t\t\tSharedSessionContractImplementor session) {\n\t\tif ( versionUpdateGroup == null ) {\n\t\t\tthrow new HibernateException( \"Cannot force version increment relative to subtype; use the root type\" );\n\t\t}\n\t\tdoVersionUpdate( null, id, nextVersion, currentVersion, getLoadedState( id, session ), session );\n\t}\n\n\tprivate @Nullable Object[] getLoadedState(Object id, SharedSessionContractImplementor session) {\n\t\treturn entityPersister.hasPartitionedSelectionMapping()\n\t\t\t\t? session.getPersistenceContextInternal()\n\t\t\t\t.getEntityHolder( session.generateEntityKey( id, entityPersister ) ).getEntityEntry().getLoadedState()\n\t\t\t\t: null;\n\t}\n\n\t@Override\n\tpublic void forceVersionIncrement(\n\t\t\tObject id,\n\t\t\tObject currentVersion,\n\t\t\tObject nextVersion,\n\t\t\tboolean batching,\n\t\t\tSharedSessionContractImplementor session) {","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java#L119-L155","documentation":"UpdateCoordinatorStandard.forceVersionIncrement(id, currentVersion, nextVersion, session) executes the dedicated version-update mutation group. For subtype persisters of a JOINED hierarchy that group is null, because the version column lives in the root table, so the coordinator refuses with HibernateException 'Cannot force version increment relative to subtype; use the root type'. The increment must be routed through the root persister, which owns the version column.","triggerScenarios":"session.buildLockRequest(LockOptions.forceVersion()).lock(entity) or EntityManager.lock(entity, LockModeType.OPTIMISTIC_FORCE_INCREMENT) where the instance's entity name resolves to a joined subclass rather than the hierarchy root; force-increment paths that reach the subtype's update coordinator directly instead of delegating upward.","commonSituations":"Version columns defined on the root of JOINED hierarchies; framework code that auto-force-increments versions on change (audit hooks, Spring lock utilities) and receives subtype instances; upgrades where delegation order in JoinedSubclassEntityPersister changed.","solutions":["Force the increment through the root type: operate on the root persister / load and lock the row as the root entity","Use a bulk versioned JPQL update on the root table (UPDATE Root SET version = version + 1 WHERE id = :id)","Upgrade Hibernate - JoinedSubclassEntityPersister delegates forceVersionIncrement to the super mapping type; ensure your version includes that delegation","Guard call sites so force-increment is only issued when the persister is the root mapping type"],"exampleFix":"// before: force-increment reaches the subtype persister\nsession.buildLockRequest(LockOptions.forceVersion()).lock(customer); // Customer extends Person (JOINED)\n\n// after: go through the root type\nsession.buildLockRequest(LockOptions.forceVersion()).lock(personRoot); // lock as Person\n// or via bulk update on the root table:\n// session.createQuery(\"update Person p set p.version = p.version + 1 where p.id = :id\")","handlingStrategy":"validation","validationCode":"// only force-increment through root persisters\nEntityPersister p = session.getEntityPersister(entity.getClass().getName(), entity);\nif (p.getSuperMappingType() != null) {\n    // subtype of a JOINED hierarchy: increment via the root type or a bulk update instead\n}","typeGuard":null,"tryCatchPattern":"try { session.buildLockRequest(LockOptions.forceVersion()).lock(entity); } catch (HibernateException e) { if (e.getMessage().contains(\"relative to subtype\")) { /* re-issue the lock through the root entity type */ } throw e; }","preventionTips":["Apply OPTIMISTIC_FORCE_INCREMENT through the hierarchy root, never through subtype references","Wrap force-increment helpers in a root-type check (getSuperMappingType() == null)","Test locking paths for every level of an inheritance hierarchy"],"tags":["hibernate","orm","versioning","locking","force-increment","joined-inheritance"],"backgroundTag":"optimistic-force-increment-misuse","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}