{"record":{"id":"a572f634b8c9134d","repo":"hibernate/hibernate-orm","slug":"property-property-is-annotated-annotation","errorCode":null,"errorMessage":"Property '${property}' is annotated '@${annotation}' but is not of type '${type}'","messagePattern":"Property '(.+?)' is annotated '@(.+?)' but is not of type '(.+?)'","errorType":"exception","errorClass":"AnnotationException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java","lineNumber":671,"sourceCode":"\t\t\t\t\tthrow new AnnotationException( \"Property '\" + qualify( holder.getPath(), name )\n\t\t\t\t\t\t\t+ \"' is annotated '@OrderBy' but is not of type 'Collection' or 'Map'\" );\n\t\t\t\t}\n\t\t\t}\n\t\t\tcheckAnnotation( MapKey.class, Map.class );\n\t\t\tcheckAnnotation( MapKeyColumn.class, Map.class );\n\t\t\tcheckAnnotation( MapKeyClass.class, Map.class );\n\t\t\tcheckAnnotation( MapKeyEnumerated.class, Map.class );\n\t\t\tcheckAnnotation( MapKeyTemporal.class, Map.class );\n\t\t\tcheckAnnotation( MapKeyColumn.class, Map.class );\n\t\t\tcheckAnnotation( MapKeyJoinColumn.class, Map.class );\n\t\t\tcheckAnnotation( MapKeyJoinColumns.class, Map.class );\n\t\t}\n\t}\n\n\tprivate void checkAnnotation(Class<? extends Annotation> annotationClass, Class<?> propertyType) {\n\t\tif ( memberDetails.hasDirectAnnotationUsage( annotationClass )\n\t\t\t\t&& !memberDetails.getType().isImplementor( propertyType ) ) {\n\t\t\tthrow new AnnotationException( \"Property '\" + qualify( holder.getPath(), name )\n\t\t\t\t\t+ \"' is annotated '@\" + annotationClass.getSimpleName()\n\t\t\t\t\t+ \"' but is not of type '\" + propertyType.getTypeName() + \"'\" );\n\t\t}\n\t}\n\n\tprivate void validateOptimisticLock(boolean excluded) {\n\t\tif ( excluded ) {\n\t\t\tif ( isVersion( memberDetails ) ) {\n\t\t\t\tthrow new AnnotationException(\"Property '\" + qualify( holder.getPath(), name )\n\t\t\t\t\t\t+ \"' is annotated '@OptimisticLock(excluded=true)' and '@Version'\" );\n\t\t\t}\n\t\t\tif ( isSimpleId( memberDetails ) ) {\n\t\t\t\tthrow new AnnotationException(\"Property '\" + qualify( holder.getPath(), name )\n\t\t\t\t\t\t+ \"' is annotated '@OptimisticLock(excluded=true)' and '@Id'\" );\n\t\t\t}\n\t\t\tif ( isEmbeddedId( memberDetails ) ) {\n\t\t\t\tthrow new AnnotationException( \"Property '\" + qualify( holder.getPath(), name )\n\t\t\t\t\t\t+ \"' is annotated '@OptimisticLock(excluded=true)' and '@EmbeddedId'\" );","sourceCodeStart":653,"sourceCodeEnd":689,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java#L653-L689","documentation":"PropertyBinder.checkAnnotation enforces that collection-oriented JPA annotations are only placed on attributes of the matching container type: @OrderColumn requires a List, and the @MapKey family (@MapKey, @MapKeyColumn, @MapKeyClass, @MapKeyEnumerated, @MapKeyTemporal, @MapKeyJoinColumn(s)) requires a Map. Violations throw AnnotationException at bootstrap with the offending annotation and required type in the message.","triggerScenarios":"@OrderColumn on a Set or Collection field (only List keeps an index); @MapKey on a List or Collection field; @MapKeyEnumerated/@MapKeyColumn on a non-Map association; changing a Map field to List (or vice versa) during refactoring while keeping the key/order annotations.","commonSituations":"Switching container types (Map to List, Set to List) when the domain model changes; copy-pasting annotations between fields of different types; generated metamodel or scaffolding code applying Map annotations to generic collections.","solutions":["Match annotation to container: keep @OrderColumn only on List, keep @MapKey* only on Map.","If the field became a List, replace the @MapKey* annotations with @MapKeyColumn-style equivalents only where valid, or drop them.","If the field became a Map, remove @OrderColumn (maps are keyed, not indexed).","Change the field type back if the annotations reflect the real intent (e.g. it should always have been a SortedMap)."],"exampleFix":"// before\n@OrderColumn(name = \"position\")\nprivate Set<Line> lines;   // Set has no index\n\n// after (choose one)\n@OrderColumn(name = \"position\")\nprivate List<Line> lines = new ArrayList<>();\n// or, if it must stay a Set:\n// remove @OrderColumn and sort with @OrderBy / SortedSet semantics","handlingStrategy":"validation","validationCode":"// Enforce annotation/container pairing before boot\nMap<Class<? extends Annotation>, Class<?>> rules = Map.of(\n        OrderColumn.class, List.class,\n        MapKey.class, Map.class,\n        MapKeyColumn.class, Map.class,\n        MapKeyClass.class, Map.class,\n        MapKeyEnumerated.class, Map.class,\n        MapKeyTemporal.class, Map.class,\n        MapKeyJoinColumn.class, Map.class,\n        MapKeyJoinColumns.class, Map.class);\nfor (Class<?> entity : annotatedClasses) {\n    for (Field f : entity.getDeclaredFields()) {\n        rules.forEach((ann, required) -> {\n            if (f.isAnnotationPresent(ann) && !required.isAssignableFrom(f.getType())) {\n                throw new IllegalStateException(f + \" has @\" + ann.getSimpleName() + \" but is not a \" + required.getSimpleName());\n            }\n        });\n    }\n}","typeGuard":"static boolean annotationMatchesType(Field f, Class<? extends Annotation> ann, Class<?> required) {\n    return !f.isAnnotationPresent(ann) || required.isAssignableFrom(f.getType());\n}","tryCatchPattern":"try {\n    SessionFactory sf = cfg.buildSessionFactory();\n} catch (AnnotationException e) {\n    throw new IllegalStateException(\"Annotation/type mismatch: \" + e.getMessage(), e);\n}","preventionTips":["@OrderColumn only on List; @MapKey* only on Map","Sweep annotations whenever a field's container type changes","Consider an ArchUnit or custom reflection rule to enforce pairing in CI"],"tags":["hibernate","jpa","mapkey","ordercolumn","annotation-misuse","collection","bootstrap"],"backgroundTag":"jpa-annotation-type-mismatch","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T14:17:55.899Z"}