{"record":{"id":"f31219bfcd1033a0","repo":"hibernate/hibernate-orm","slug":"user-defined-version-types-not-supported-for-incre","errorCode":null,"errorMessage":"User-defined version types not supported for increment option","messagePattern":"User-defined version types not supported for increment option","errorType":"exception","errorClass":"SemanticException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/query/sqm/sql/spi/BaseSqmToSqlAstConverter.java","lineNumber":916,"sourceCode":"\t\tfinally {\n\t\t\tpopProcessingStateStack();\n\t\t\tthis.currentSqmStatement = oldSqmStatement;\n\t\t\tthis.cteContainer = oldCteContainer;\n\t\t}\n\t}\n\n\tpublic void addVersionedAssignment(Consumer<Assignment> assignmentConsumer, SqmUpdateStatement<?> sqmStatement) {\n\t\tif ( sqmStatement.isVersioned() ) {\n\t\t\tfinal var persister =\n\t\t\t\t\tgetMappingMetamodel()\n\t\t\t\t\t\t\t.findEntityDescriptor( sqmStatement.getTarget().getEntityName() );\n\t\t\tif ( !persister.isVersioned() ) {\n\t\t\t\tthrow new SemanticException( \"Increment option specified for update of non-versioned entity\" );\n\t\t\t}\n\n\t\t\tfinal var versionType = persister.getVersionType();\n\t\t\tif ( versionType instanceof UserVersionType ) {\n\t\t\t\tthrow new SemanticException( \"User-defined version types not supported for increment option\" );\n\t\t\t}\n\n\t\t\tcurrentClauseStack.push( Clause.SET );\n\t\t\tfinal var versionMapping = persister.getVersionMapping();\n\t\t\tfinal var targetColumnReferences = BasicValuedPathInterpretation.from(\n\t\t\t\t\t(SqmBasicValuedSimplePath<?>)\n\t\t\t\t\t\t\tSqmExpressionHelper.get( sqmStatement.getRoot(),\n\t\t\t\t\t\t\t\t\tversionMapping.getPartName() ),\n\t\t\t\t\tthis,\n\t\t\t\t\tjpaQueryComplianceEnabled\n\t\t\t).getColumnReferences();\n\t\t\tcurrentClauseStack.pop();\n\t\t\tassert targetColumnReferences.size() == 1;\n\n\t\t\tfinal var versionColumn = targetColumnReferences.get( 0 );\n\t\t\tfinal var value =\n\t\t\t\t\tversionMapping.getJdbcMapping().getJdbcType().isTemporal()\n\t\t\t\t\t\t\t? new VersionTypeSeedParameterSpecification( versionMapping )","sourceCodeStart":898,"sourceCodeEnd":934,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/spi/BaseSqmToSqlAstConverter.java#L898-L934","documentation":"For 'UPDATE VERSIONED', Hibernate increments the version column arithmetically in SQL (version = version + 1) when the version is numeric. If the version type is a UserVersionType (a custom user-supplied version strategy), Hibernate cannot derive a SQL increment expression for it, so addVersionedAssignment throws SemanticException('User-defined version types not supported for increment option'). The entity is versioned, but its version is user-defined, which blocks the bulk increment path.","triggerScenarios":"Entity mapped with @Version on a field whose type is resolved through a custom UserType implementing UserVersionType (e.g. a legacy string GUID-based version, custom timestamp), then executing 'UPDATE VERSIONED ...'; reusing a legacy Hibernate 5 custom version type after upgrading to 6/7 where addVersionedAssignment was reworked to SQL-side increments.","commonSituations":"Legacy applications with custom version strategies (homegrown OptimisticLockStyle types); migrating from Hibernate 5 where versioned bulk updates with user types were handled differently; teams adding the new standardized VERSIONED keyword (JPA 3.2) to entities that predate built-in version types.","solutions":["Switch the version to a built-in incrementable type: @Version private long version; or @Version private Instant/LocalDateTime/... timestamp","Remove VERSIONED from the bulk update and manage the custom version yourself: set version = :newVersion and filter where version = :oldVersion in the same statement","Implement the increment through a custom SQL update (session.createNativeMutationQuery) where you control the version expression","If the custom type merely wraps a number, replace it with the plain numeric field and convert at the boundaries"],"exampleFix":"-- before\n@Entity class Doc { @Version MyStringVersion v; } // UserVersionType\nUPDATE VERSIONED Doc d SET d.title = :t\n-- after\n@Entity class Doc { @Version long v; }\nUPDATE VERSIONED Doc d SET d.title = :t","handlingStrategy":"validation","validationCode":"// Reject VERSIONED updates when the version type is user-defined\nEntityType<?> et = em.getMetamodel().entity(Doc.class);\nSingularAttribute<?, ?> ver = et.getSingularAttributes().stream()\n        .filter(SingularAttribute::isVersion).findFirst().orElseThrow();\n// if a custom UserType backs it, require the non-VERSIONED form","typeGuard":"static boolean builtInVersion(EntityManager em, Class<?> c) {\n    Class<?> jt = em.getMetamodel().entity(c).getSingularAttributes().stream()\n        .filter(a -> a.isVersion()).findFirst().map(a -> a.getJavaType()).orElse(null);\n    return Number.class.isAssignableFrom(jt) || java.util.Date.class.isAssignableFrom(jt)\n        || java.time.temporal.Temporal.class.isAssignableFrom(jt);\n}","tryCatchPattern":"catch (SemanticException e) { if (e.getMessage().contains(\"User-defined version\")) { /* drop VERSIONED and increment manually */ } else throw e; }","preventionTips":["Prefer long/timestamp version columns over UserVersionType","If a custom version type is mandatory, never use the VERSIONED keyword","Replace legacy UserVersionTypes during Hibernate upgrades"],"tags":["hibernate","hql","bulk-update","optimistic-locking","user-version-type","usertype","version"],"backgroundTag":"custom-version-type-unsupported","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}