{"record":{"id":"c3d734626e953184","repo":"hibernate/hibernate-orm","slug":"property-s-s-is-not-a-collection-and-may-not-b","errorCode":null,"errorMessage":"Property '%s.%s' is not a collection and may not be a '@OneToMany', '@ManyToMany', or '@ElementCollection'","messagePattern":"Property '(.+?)\\.(.+?)' is not a collection and may not be a '@OneToMany', '@ManyToMany', or '@ElementCollection'","errorType":"exception","errorClass":"AnnotationException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java","lineNumber":1041,"sourceCode":"\t\t\treturn CollectionClassification.MAP;\n\t\t}\n\n\t\tif ( java.util.Collection.class.isAssignableFrom( semanticJavaType ) ) {\n\t\t\treturn property.hasDirectAnnotationUsage( CollectionId.class )\n\t\t\t\t\t? CollectionClassification.ID_BAG\n\t\t\t\t\t: CollectionClassification.BAG;\n\t\t}\n\n\t\treturn null;\n\t}\n\n\tprivate static Class<?> determineSemanticJavaType(MemberDetails property) {\n\t\tif ( property.isPlural() ) {\n\t\t\treturn inferCollectionClassFromSubclass(\n\t\t\t\t\tproperty.getType().determineRawClass().toJavaClass() );\n\t\t}\n\t\telse {\n\t\t\tthrow new AnnotationException(\n\t\t\t\t\tString.format(\n\t\t\t\t\t\t\tLocale.ROOT,\n\t\t\t\t\t\t\t\"Property '%s.%s' is not a collection and may not be a '@OneToMany', '@ManyToMany', or '@ElementCollection'\",\n\t\t\t\t\t\t\tproperty.getDeclaringType().getName(),\n\t\t\t\t\t\t\tproperty.resolveAttributeName()\n\t\t\t\t\t)\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate static Class<?> inferCollectionClassFromSubclass(Class<?> clazz) {\n\t\tfor ( var priorityClass : INFERRED_CLASS_PRIORITY ) {\n\t\t\tif ( priorityClass.isAssignableFrom( clazz ) ) {\n\t\t\t\treturn priorityClass;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}","sourceCodeStart":1023,"sourceCodeEnd":1059,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java#L1023-L1059","documentation":"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.","triggerScenarios":"A scalar field (single object reference, primitive, or wrapper) is annotated with @OneToMany, @ManyToMany, or @ElementCollection, so determineSemanticJavaType hits the else branch during classification.","commonSituations":"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.","solutions":["For a single reference, use @ManyToOne (or @OneToOne) with @JoinColumn instead of a collection annotation","If a collection is intended, declare the field as a Collection/List/Set/Map type","In bidirectional relations, put the collection annotation (@OneToMany mappedBy=...) on the many-valued side and @ManyToOne on the single-valued side"],"exampleFix":"// before\n@Entity\nclass Book {\n    @OneToMany(mappedBy = \"book\")   // error: single reference, not a collection\n    Author author;\n}\n\n// after\n@Entity\nclass Book {\n    @ManyToOne\n    @JoinColumn(name = \"author_id\")\n    Author author;\n}","handlingStrategy":"validation","validationCode":"// Collection annotations require a plural field type\nstatic void checkPluralFields(Class<?>... entities) {\n    for ( Class<?> c : entities ) {\n        for ( Field f : c.getDeclaredFields() ) {\n            boolean collectionAnn = f.isAnnotationPresent( OneToMany.class )\n                || f.isAnnotationPresent( ManyToMany.class )\n                || f.isAnnotationPresent( ElementCollection.class );\n            boolean plural = Collection.class.isAssignableFrom( f.getType() )\n                || Map.class.isAssignableFrom( f.getType() )\n                || f.getType().isArray();\n            if ( collectionAnn && !plural ) {\n                throw new IllegalStateException( \"Collection annotation on non-collection field \"\n                    + c.getName() + \".\" + f.getName() );\n            }\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Map single references with @ManyToOne/@OneToOne - the collection annotations are only for plural attributes","In bidirectional pairs, the @OneToMany(mappedBy=...) side is always the collection field","Add the plural-type scan to mapping tests to catch copy-paste mistakes early"],"tags":["hibernate","jpa","one-to-many","many-to-one","collection-type","annotation-binding"],"backgroundTag":"association-on-non-collection","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}