{"record":{"id":"fe81abc2144e4f6b","repo":"hibernate/hibernate-orm","slug":"convert-placed-on-entity-mappedsuperclass-must","errorCode":null,"errorMessage":"@Convert placed on @Entity/@MappedSuperclass must define attributeName","messagePattern":"@Convert placed on @Entity/@MappedSuperclass must define attributeName","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java","lineNumber":111,"sourceCode":"\t\t\treturn;\n\t\t}\n\n\t\t// collect superclass info first\n\t\tcollectAttributeConversionInfo( infoMap, entityClassDetails.getSuperClass() );\n\n\t\tfinal var modelContext = getSourceModelContext();\n\t\tfinal boolean canContainConvert =\n\t\t\t\tentityClassDetails.hasAnnotationUsage( jakarta.persistence.Entity.class, modelContext )\n\t\t\t\t|| entityClassDetails.hasAnnotationUsage( jakarta.persistence.MappedSuperclass.class, modelContext )\n\t\t\t\t|| entityClassDetails.hasAnnotationUsage( jakarta.persistence.Embeddable.class, modelContext );\n\t\tif ( ! canContainConvert ) {\n\t\t\treturn;\n\t\t}\n\n\t\tentityClassDetails.forEachAnnotationUsage( Convert.class, modelContext, (usage) -> {\n\t\t\tfinal var info = new AttributeConversionInfo( usage, entityClassDetails );\n\t\t\tif ( isEmpty( info.getAttributeName() ) ) {\n\t\t\t\tthrow new IllegalStateException( \"@Convert placed on @Entity/@MappedSuperclass must define attributeName\" );\n\t\t\t}\n\t\t\tinfoMap.put( info.getAttributeName(), info );\n\t\t} );\n\t}\n\n\t@Override\n\tpublic void startingProperty(MemberDetails property) {\n\t\tif ( property != null ) {\n\t\t\tfinal String propertyName = property.resolveAttributeName();\n\t\t\tif ( !attributeConversionInfoMap.containsKey( propertyName ) ) {\n\t\t\t\tproperty.forEachAnnotationUsage( Convert.class, getSourceModelContext(), (usage) -> {\n\t\t\t\t\tfinal var info = new AttributeConversionInfo( usage, property );\n\t\t\t\t\tfinal String infoAttributeName = info.getAttributeName();\n\t\t\t\t\tfinal String path =\n\t\t\t\t\t\t\tisEmpty( infoAttributeName )\n\t\t\t\t\t\t\t\t\t? propertyName\n\t\t\t\t\t\t\t\t\t: propertyName + '.' + infoAttributeName;\n\t\t\t\t\tattributeConversionInfoMap.put( path, info );","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java#L93-L129","documentation":"A class-level @Convert on an @Entity, @MappedSuperclass, or @Embeddable must state which attribute it converts through attributeName, because Hibernate cannot infer the target attribute from a class placement. ClassPropertyHolder throws this IllegalStateException (note: not AnnotationException) while collecting AttributeConversionInfo when info.getAttributeName() is empty. Field-level @Convert placements do not need attributeName.","triggerScenarios":"Writing @Convert(converter = MyConverter.class) directly on an entity/mapped-superclass/embeddable class without attributeName. The class must carry @Entity, @MappedSuperclass, or @Embeddable for the check to run at all.","commonSituations":"Moving @Convert from a field to the class to override a converter for an embedded sub-attribute and forgetting attributeName; applying @Converter(autoApply = true) documentation examples that omit attributeName at class level; refactoring converters during a JPA migration.","solutions":["Add attributeName: @Convert(converter = OrderStatusConverter.class, attributeName = \"status\")","Or move the @Convert annotation down onto the target field where attributeName is not needed","For embedded sub-attributes use a dotted path in attributeName, e.g. attributeName = \"address.city\""],"exampleFix":"// before\n@Convert(converter = OrderStatusConverter.class)  // class-level, no attributeName -> error\n@Entity\npublic class Order {\n    OrderStatus status;\n}\n\n// after\n@Convert(converter = OrderStatusConverter.class, attributeName = \"status\")\n@Entity\npublic class Order {\n    OrderStatus status;\n}","handlingStrategy":"validation","validationCode":"// Fail fast in a test: class-level @Convert must carry attributeName\nstatic void checkClassLevelConverts(Class<?>... entities) {\n    for ( Class<?> c : entities ) {\n        for ( Convert convert : c.getAnnotationsByType( Convert.class ) ) {\n            if ( convert.attributeName().isBlank() ) {\n                throw new IllegalStateException( \"Class-level @Convert without attributeName on \"\n                    + c.getName() );\n            }\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Prefer field-level @Convert - it needs no attributeName","When hoisting @Convert to class level (e.g. for embedded sub-attributes), always pair it with attributeName, including dotted paths","Run a SessionFactory bootstrap test so converter wiring errors surface in CI"],"tags":["hibernate","jpa","attribute-converter","convert","annotation-binding"],"backgroundTag":"missing-annotation-attribute","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}