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 classes

View on GitHub (pinned to fad1729dce)

Solutions

  1. Remove @Version from the embeddable member and declare @Version on the owning entity.
  2. If the embeddable tracks timestamps only, keep createdAt/updatedAt with @CreationTimestamp/@UpdateTimestamp and no @Version.
  3. 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

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


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/1cac73a5765df839. Report an issue: GitHub.