hibernate/hibernate-orm · error · AnnotationException

Member '" + memberDetails.getName() + "' of embeddable class

Error message

Member '" + memberDetails.getName() + "' of embeddable class '" + propertyHolder.getClassName() + "' is annotated '@Id'

What it means

When an embeddable is used as a regular @Embedded (not as an @EmbeddedId aggregate), none of its members may carry @Id — identifiers may only appear at the entity level or inside an @EmbeddedId component. PropertyBinder detects the stray @Id while binding the property and fails bootstrap.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java:869

		}
	}

	private static void buildProperty(
			PropertyHolder propertyHolder,
			Nullability nullability,
			PropertyData inferredData,
			EntityBinder entityBinder,
			boolean isIdentifierMapper,
			boolean isComponentEmbedded,
			boolean inSecondPass,
			MetadataBuildingContext context,
			Map<ClassDetails, InheritanceState> inheritanceStatePerClass) {

		final var memberDetails = inferredData.getAttributeMember();

		if ( isPropertyOfRegularEmbeddable( propertyHolder, isComponentEmbedded )
				&& isSimpleId( memberDetails ) ) {
			throw new AnnotationException("Member '" + memberDetails.getName()
					+ "' of embeddable class '" + propertyHolder.getClassName() + "' is annotated '@Id'");
		}

		final var attributeTypeDetails =
				memberDetails.isPlural()
						? memberDetails.getType()
						: inferredData.getClassOrElementType();

		final var propertyBinder = propertyBinder(
				propertyHolder,
				inferredData,
				entityBinder,
				isIdentifierMapper,
				context,
				inheritanceStatePerClass,
				attributeTypeDetails
		);

View on GitHub (pinned to fad1729dce)

Solutions

  1. Remove @Id from the member of the embeddable when the class is used via @Embedded.
  2. If both usages are needed, split into two classes: one clean embeddable, one @EmbeddedId-specific component.
  3. If the field really is the entity's identity, use @EmbeddedId on the entity property instead of @Embedded.

Example fix

// before
@Embeddable
public class Money {
    @Id            // rejected in a regular embeddable
    private Long id;
    private BigDecimal amount;
}
@Entity
public class Deal {
    @Embedded
    private Money price;
}

// after
@Embeddable
public class Money {
    private BigDecimal amount;
}
@Entity
public class Deal {
    @Id @GeneratedValue
    private Long id;
    @Embedded
    private Money price;
}
Defensive patterns

Strategy: validation

Validate before calling

// Regular embeddables must not contain @Id members
for (Class<?> cls : annotatedClasses) {
    if (!cls.isAnnotationPresent(Embeddable.class)) continue;
    boolean usedAsEmbeddedId = annotatedClasses.stream()
            .flatMap(e -> Stream.of(e.getDeclaredFields()))
            .filter(f -> f.isAnnotationPresent(Embedded.class))
            .anyMatch(f -> f.getType().equals(cls));
    if (usedAsEmbeddedId) continue;
    for (Field f : cls.getDeclaredFields()) {
        if (f.isAnnotationPresent(Id.class)) {
            throw new IllegalStateException("Regular embeddable " + cls.getName() + " has @Id member " + f.getName());
        }
    }
}

Try / catch

try {
    SessionFactory sf = cfg.buildSessionFactory();
} catch (AnnotationException e) {
    throw new IllegalStateException("Embeddable contains @Id: " + e.getMessage(), e);
}

Prevention

When it happens

Trigger: A class reused both as @EmbeddedId component and as plain @Embeddable, with @Id left inside; an embeddable copied from an id-class template; converting an @EmbeddedId to @Embedded without removing the @Id from the embedded field.

Common situations: Sharing one embeddable class between composite-id usage and regular embedded usage; refactoring from composite keys to surrogate keys; scaffolding tools that generate @Id on embeddable members.

Related errors


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