hibernate/hibernate-orm · error · AnnotationException

Property '" + qualify( classDetails.getName(), attributeMemb

Error message

Property '" + qualify( classDetails.getName(), attributeMemberDetails.getName() ) + "' has an unbound type and no explicit target entity (resolve this generics usage issue or set an explicit target attribute with '@OneToMany(target=)' or use an explicit '@Type')

What it means

verifyAndInitializePersistentAttributes checks that every persistent attribute's type resolves; if the member's relative type is still an unbound type variable and discoverTypeWithoutReflection finds no explicit target, an AnnotationException is thrown naming the qualified property. Hibernate cannot determine the entity class for the generic member, so it asks you to bind the type parameter or declare a target explicitly.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyContainer.java:295

		return classLevelAccessType;
	}

	public Iterable<MemberDetails> propertyIterator() {
		return attributeMembers;
	}

	private static List<MemberDetails> verifyAndInitializePersistentAttributes(
			ClassDetails classDetails,
			TypeVariableScope typeAtStake,
			Map<String, MemberDetails> attributeMemberMap) {
		final ArrayList<MemberDetails> output = new ArrayList<>( attributeMemberMap.size() );
		for ( var attributeMemberDetails : attributeMemberMap.values() ) {
			if ( !attributeMemberDetails.resolveRelativeType( typeAtStake ).isResolved()
					&& !discoverTypeWithoutReflection( attributeMemberDetails ) ) {
				final String msg = "Property '" + qualify( classDetails.getName(), attributeMemberDetails.getName() ) +
						"' has an unbound type and no explicit target entity (resolve this generics usage issue" +
						" or set an explicit target attribute with '@OneToMany(target=)' or use an explicit '@Type')";
				throw new AnnotationException( msg );
			}
			output.add( attributeMemberDetails );
		}
		return toSmallList( output );
	}

	private AccessType determineLocalClassDefinedAccessStrategy() {
		final var access = classDetails.getDirectAnnotationUsage( Access.class );
		return access == null ? AccessType.DEFAULT : AccessType.getAccessStrategy( access.value() );
	}

	private static boolean discoverTypeWithoutReflection(MemberDetails memberDetails) {
		if ( memberDetails.hasDirectAnnotationUsage( TargetEmbeddable.class ) ) {
			return true;
		}

		if ( memberDetails.hasDirectAnnotationUsage( Basic.class ) ) {
			return true;

View on GitHub (pinned to fad1729dce)

Solutions

  1. Add an explicit target: @OneToMany(targetEntity = Item.class) (or @ManyToOne targetEntity, @OneToOne targetEntity) on the generic member.
  2. Add @Type(ClassThatHandlesIt.class) if the member is a basic/composite value rather than an association.
  3. Bind the type variable by making the concrete subclass the mapped entity and passing the real type argument to the generic base.
  4. Restructure: move the generic collection out of the mapped hierarchy into concrete subclasses that declare typed members.

Example fix

// before
public abstract class AbstractOrder<T extends Item> {
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    private List<T> items; // T unbound
}

// after
public abstract class AbstractOrder<T extends Item> {
    @OneToMany(targetEntity = Item.class, mappedBy = "order", cascade = CascadeType.ALL)
    private List<T> items;
}
// or map concrete subclasses: class SalesOrder extends AbstractOrder<SalesItem> and ensure the
// type argument is resolvable
Defensive patterns

Strategy: validation

Validate before calling

// reject unbound generic members on entity classes before Hibernate sees them
for (Field f : Order.class.getDeclaredFields()) {
    if (isPersistent(f) && f.getGenericType() instanceof TypeVariable) {
        throw new IllegalStateException("Unbound generic member: " + f
            + " - set targetEntity/@Type or bind the type argument");
    }
}

Type guard

boolean isResolvedMember(Field f) {
    Type t = f.getGenericType();
    return !(t instanceof TypeVariable) && !(t instanceof Class<?> c && c.getTypeParameters().length > 0);
}

Try / catch

try {
    metadata = sources.buildMetadata();
} catch (AnnotationException e) {
    failBuild("Unresolved generic type in mapping: " + e.getMessage());
}

Prevention

When it happens

Trigger: A mapped class declares @OneToMany List<T> items or @ElementCollection Set<T> while T is never bound to a concrete entity (generic base class used directly as an entity, or subclass type argument lost); no @OneToMany(targetEntity=...), @ManyToOne(targetEntity=...), @Type, or equivalent is present. Fires during PropertyContainer initialization, before any DDL or SQL runs.

Common situations: Abstract generic DAO/base-entity classes reused as mapped superclasses without concrete type arguments; entities inside generic hierarchies where Jandex/reflection cannot see the resolved argument (wildcards, recursive generics, enhanced proxy classes); third-party base libraries with @OneToMany on T; upgrading Hibernate versions that previously guessed the type from reflection.

Related errors


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