{"record":{"id":"ef4b5d02d88d4c51","repo":"hibernate/hibernate-orm","slug":"named-type-typeimplementorclass-did-not-imple","errorCode":null,"errorMessage":"Named type [${typeImplementorClass}] did not implement BasicType nor UserType","messagePattern":"Named type \\[(.+?)\\] did not implement BasicType nor UserType","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java","lineNumber":246,"sourceCode":"\t\t// Series of backward compatible special cases\n\t\treturn resolveLegacyCases( typeImplementorClass, indicators, typeConfiguration );\n\t}\n\n\tprivate static <T> BasicValue.Resolution<T> resolveLegacyCases(\n\t\t\tClass<T> typeImplementorClass, JdbcTypeIndicators indicators, TypeConfiguration typeConfiguration) {\n\t\treturn createBasicTypeResolution( getLegacyType( typeImplementorClass ),\n\t\t\t\ttypeImplementorClass, indicators, typeConfiguration );\n\t}\n\n\tprivate static <T> BasicType<T> getLegacyType(Class<T> typeImplementorClass) {\n\t\tif ( Serializable.class.isAssignableFrom( typeImplementorClass ) ) {\n\t\t\treturn new SerializableType( typeImplementorClass );\n\t\t}\n\t\telse if ( typeImplementorClass.isInterface() ) {\n\t\t\treturn (BasicType<T>) new JavaObjectType();\n\t\t}\n\t\telse {\n\t\t\tthrow new IllegalArgumentException( \"Named type [\" + typeImplementorClass\n\t\t\t\t\t\t\t\t\t\t\t\t+ \"] did not implement BasicType nor UserType\" );\n\t\t}\n\t}\n\n\tprivate static <T> BasicValue.Resolution<T> createBasicTypeResolution(\n\t\t\tBasicType<T> type,\n\t\t\tClass<T> typeImplementorClass,\n\t\t\tJdbcTypeIndicators indicators,\n\t\t\tTypeConfiguration typeConfiguration) {\n\t\tfinal var jtd = typeConfiguration.getJavaTypeRegistry().resolveDescriptor( typeImplementorClass );\n\t\tfinal var jdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( Types.VARBINARY );\n\t\tfinal var basicType = typeConfiguration.getBasicTypeRegistry().resolve( jtd, jdbcType );\n\t\tfinal var resolved = resolveSqlTypeIndicators( indicators, basicType, jtd );\n\n\t\treturn new BasicValue.Resolution<>() {\n\t\t\t@Override\n\t\t\tpublic JdbcMapping getJdbcMapping() {\n\t\t\t\treturn resolved;","sourceCodeStart":228,"sourceCodeEnd":264,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java#L228-L264","documentation":"When a named type (from hbm.xml <typedef>/<type name=.../>) resolves to a class that is not a BasicType/UserType implementation, TypeDefinition falls back to 'legacy' adaptation: Serializable classes become SerializableType, interfaces become JavaObjectType, and everything else throws IllegalArgumentException('Named type [X] did not implement BasicType nor UserType'). Plain non-serializable classes cannot be adapted into a Hibernate basic type.","triggerScenarios":"A <typedef class=\"com.acme.Money\"/> or @Type(...)/hbm <type name=\"...\"> pointing at a class that implements none of Hibernate's type interfaces and is neither Serializable nor an interface — most often the value class itself instead of a UserType that wraps it, or a typo that resolves to some other plain class.","commonSituations":"Mapping a custom value object directly by class name instead of writing a UserType; classpaths where the intended UserType class was renamed so the FQN now hits an unrelated class; legacy hbm.xml brought forward from very old Hibernate versions.","solutions":["Write a custom type implementing org.hibernate.usertype.UserType (or CompositeUserType) for the value class and reference that type's class name in the mapping","Alternatively make the value class implement java.io.Serializable so the legacy SerializableType adaptation applies (stores as serialized binary — usually the worse option)","Verify the FQN in the mapping is the type class, not the domain value class, and that it exists on the classpath"],"exampleFix":"<!-- before -->\n<typedef name=\"money\" class=\"com.acme.Money\"/> <!-- plain value class -->\n\n<!-- after -->\n<typedef name=\"money\" class=\"com.acme.MoneyType\"/> <!-- implements UserType -->","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"static boolean isAdaptableAsHibernateType(Class<?> c) {\n    return org.hibernate.type.BasicType.class.isAssignableFrom(c)\n        || org.hibernate.usertype.UserType.class.isAssignableFrom(c)\n        || org.hibernate.usertype.CompositeUserType.class.isAssignableFrom(c)\n        || java.io.Serializable.class.isAssignableFrom(c)\n        || c.isInterface();\n}","tryCatchPattern":"catch (IllegalArgumentException e) {\n    if (String.valueOf(e.getMessage()).contains(\"did not implement BasicType nor UserType\")) {\n        throw new IllegalStateException(\"Referenced type class must implement UserType/BasicType - fix the typedef class\", e);\n    }\n    throw e;\n}","preventionTips":["Reference custom UserType implementations in <typedef class=...>, never the value class","Add a unit test that resolves every configured typedef at startup"],"tags":["hbm-xml","usertype","custom-type","mapping"],"backgroundTag":"custom-type-not-implemented","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}