hibernate/hibernate-orm · error · AnnotationException
Embedded class '" + propertyHolder.getEntityName() + "' may
Error message
Embedded class '" + propertyHolder.getEntityName() + "' may not have a property annotated '@Version'
What it means
@Version is only valid on entity classes; an embeddable/component class has no independent optimistic-lock lifecycle of its own, so checkVersionProperty throws AnnotationException when the property holder is not an entity. The version must live on the owning entity.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java:1103
//we know the property is on the actual entity
rootClass.setDeclaredVersion( property );
}
rootClass.setOptimisticLockStyle( OptimisticLockStyle.VERSION );
}
private static void checkVersionProperty(PropertyHolder propertyHolder, boolean isIdentifierMapper) {
if ( isIdentifierMapper ) {
throw new AnnotationException( "Class '" + propertyHolder.getEntityName()
+ "' is annotated '@IdClass' and may not have a property annotated '@Version'"
);
}
if ( !( propertyHolder.getPersistentClass() instanceof RootClass ) ) {
throw new AnnotationException( "Entity '" + propertyHolder.getEntityName()
+ "' is a subclass in an entity class hierarchy and may not have a property annotated '@Version'" );
}
if ( !propertyHolder.isEntity() ) {
throw new AnnotationException( "Embedded class '" + propertyHolder.getEntityName()
+ "' may not have a property annotated '@Version'" );
}
}
private AnnotatedColumns bindBasicOrComposite(
PropertyHolder propertyHolder,
Nullability nullability,
PropertyData inferredData,
EntityBinder entityBinder,
boolean isIdentifierMapper,
boolean isComponentEmbedded,
ColumnsBuilder columnsBuilder,
AnnotatedColumns columns,
ClassDetails returnedClass) {
final var memberDetails = inferredData.getAttributeMember();
if ( propertyHolder.isEntity() && propertyHolder.getPersistentClass().isAbstract() ) {
// When the type of the member is a type variable, we mark it as generic for abstract classesView on GitHub (pinned to fad1729dce)
Solutions
- Remove @Version from the embeddable member and declare @Version on the owning entity.
- If the embeddable tracks timestamps only, keep createdAt/updatedAt with @CreationTimestamp/@UpdateTimestamp and no @Version.
- Alternatively map the component's version column as a plain immutable field managed by the application.
Example fix
// before
@Embeddable
public class Audit {
@Version // rejected: holder is not an entity
private int version;
}
// after
@Embeddable
public class Audit { /* timestamps only */ }
@Entity
public class Document {
@Embedded
private Audit audit;
@Version
private int version;
} Defensive patterns
Strategy: validation
Validate before calling
// Embeddables must not declare @Version
for (Class<?> cls : annotatedClasses) {
if (cls.isAnnotationPresent(Embeddable.class)) {
for (Field f : cls.getDeclaredFields()) {
if (f.isAnnotationPresent(Version.class)) {
throw new IllegalStateException("Embeddable " + cls.getName() + " declares @Version field " + f.getName());
}
}
}
} Try / catch
try {
SessionFactory sf = cfg.buildSessionFactory();
} catch (AnnotationException e) {
throw new IllegalStateException("@Version in embeddable: " + e.getMessage(), e);
} Prevention
- Keep version state on entities; embeddables carry values, not lifecycle
- Use @CreationTimestamp/@UpdateTimestamp inside audit embeddables instead of @Version
When it happens
Trigger: A field annotated @Version inside an @Embeddable class (e.g. an audit embeddable reused across entities); embeddables assembled from copied entity code that retained the version field.
Common situations: Shared 'Auditable' embeddables that bundle createdAt/updatedAt plus a version column; refactoring that moved the version property into a component; scaffolding that treats embeddables like mini-entities.
Related errors
- Property '${property}' is annotated '@OptimisticLock(exclude
- Class '" + propertyHolder.getEntityName() + "' is annotated
- Property '${property}' is annotated '@OptimisticLock(exclude
- Property '${property}' is annotated '@OptimisticLock(exclude
- Member '" + memberDetails.getName() + "' of embeddable class
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/1cac73a5765df839.
Report an issue: GitHub.