hibernate/hibernate-orm · error · AnnotationException

Property '%s.%s' is not a collection and may not be a '@OneT

Error message

Property '%s.%s' is not a collection and may not be a '@OneToMany', '@ManyToMany', or '@ElementCollection'

What it means

The collection annotations @OneToMany, @ManyToMany, and @ElementCollection require a plural attribute. When determineSemanticJavaType is asked for the collection Java type of a non-plural property (property.isPlural() is false), Hibernate throws this AnnotationException naming the property and its declaring class.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java:1041

			return CollectionClassification.MAP;
		}

		if ( java.util.Collection.class.isAssignableFrom( semanticJavaType ) ) {
			return property.hasDirectAnnotationUsage( CollectionId.class )
					? CollectionClassification.ID_BAG
					: CollectionClassification.BAG;
		}

		return null;
	}

	private static Class<?> determineSemanticJavaType(MemberDetails property) {
		if ( property.isPlural() ) {
			return inferCollectionClassFromSubclass(
					property.getType().determineRawClass().toJavaClass() );
		}
		else {
			throw new AnnotationException(
					String.format(
							Locale.ROOT,
							"Property '%s.%s' is not a collection and may not be a '@OneToMany', '@ManyToMany', or '@ElementCollection'",
							property.getDeclaringType().getName(),
							property.resolveAttributeName()
					)
			);
		}
	}

	private static Class<?> inferCollectionClassFromSubclass(Class<?> clazz) {
		for ( var priorityClass : INFERRED_CLASS_PRIORITY ) {
			if ( priorityClass.isAssignableFrom( clazz ) ) {
				return priorityClass;
			}
		}
		return null;
	}

View on GitHub (pinned to fad1729dce)

Solutions

  1. For a single reference, use @ManyToOne (or @OneToOne) with @JoinColumn instead of a collection annotation
  2. If a collection is intended, declare the field as a Collection/List/Set/Map type
  3. In bidirectional relations, put the collection annotation (@OneToMany mappedBy=...) on the many-valued side and @ManyToOne on the single-valued side

Example fix

// before
@Entity
class Book {
    @OneToMany(mappedBy = "book")   // error: single reference, not a collection
    Author author;
}

// after
@Entity
class Book {
    @ManyToOne
    @JoinColumn(name = "author_id")
    Author author;
}
Defensive patterns

Strategy: validation

Validate before calling

// Collection annotations require a plural field type
static void checkPluralFields(Class<?>... entities) {
    for ( Class<?> c : entities ) {
        for ( Field f : c.getDeclaredFields() ) {
            boolean collectionAnn = f.isAnnotationPresent( OneToMany.class )
                || f.isAnnotationPresent( ManyToMany.class )
                || f.isAnnotationPresent( ElementCollection.class );
            boolean plural = Collection.class.isAssignableFrom( f.getType() )
                || Map.class.isAssignableFrom( f.getType() )
                || f.getType().isArray();
            if ( collectionAnn && !plural ) {
                throw new IllegalStateException( "Collection annotation on non-collection field "
                    + c.getName() + "." + f.getName() );
            }
        }
    }
}

Prevention

When it happens

Trigger: A scalar field (single object reference, primitive, or wrapper) is annotated with @OneToMany, @ManyToMany, or @ElementCollection, so determineSemanticJavaType hits the else branch during classification.

Common situations: Using @OneToMany on a single back-reference that should be @ManyToOne; copy-paste of collection mappings onto single-valued fields; generics stripped during refactor so a helper exposes a single type; misunderstanding which side carries the collection annotation in bidirectional relationships.

Related errors


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