{"record":{"id":"79e0e5794408a290","repo":"hibernate/hibernate-orm","slug":"database-does-not-support-user-defined-types-remo","errorCode":null,"errorMessage":"Database does not support user-defined types (remove '@Struct' annotation)","messagePattern":"Database does not support user-defined types \\(remove '@Struct' annotation\\)","errorType":"exception","errorClass":"MappingException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentSecondPass.java","lineNumber":90,"sourceCode":"\t\t// Sort the component properties early to ensure the aggregated\n\t\t// columns respect the same order as the component's properties\n\t\tfinal int[] originalOrder = component.sortProperties();\n\t\t// Compute aggregated columns since we have to replace them in the table with the aggregate column\n\t\tfinal List<Column> aggregatedColumns = component.getAggregatedColumns();\n\t\tfinal AggregateColumn aggregateColumn = component.getAggregateColumn();\n\n\t\tensureInitialized( metadataCollector, aggregateColumn );\n\t\tvalidateSupportedColumnTypes( propertyHolder.getPath(), component );\n\n\t\tfinal QualifiedName structName = component.getStructName();\n\t\tfinal boolean addAuxiliaryObjects;\n\t\tif ( structName != null ) {\n\t\t\tfinal Namespace namespace = database.locateNamespace(\n\t\t\t\t\tstructName.getCatalogName(),\n\t\t\t\t\tstructName.getSchemaName()\n\t\t\t);\n\t\t\tif ( !database.getDialect().supportsUserDefinedTypes() ) {\n\t\t\t\tthrow new MappingException( \"Database does not support user-defined types (remove '@Struct' annotation)\" );\n\t\t\t}\n\t\t\tfinal var udt = new UserDefinedObjectType( \"orm\", namespace, structName.getObjectName() );\n\t\t\tfor ( var aggregatedColumn : aggregatedColumns ) {\n\t\t\t\tudt.addColumn( aggregatedColumn );\n\t\t\t}\n\t\t\tfinal var registeredUdt = namespace.createUserDefinedType( structName.getObjectName(), name -> udt );\n\t\t\tif ( registeredUdt == udt ) {\n\t\t\t\taddAuxiliaryObjects = true;\n\t\t\t\torderColumns( registeredUdt, originalOrder );\n\t\t\t}\n\t\t\telse {\n\t\t\t\taddAuxiliaryObjects =\n\t\t\t\t\t\tisAggregateArray()\n\t\t\t\t\t\t\t&& namespace.locateUserDefinedArrayType( Identifier.toIdentifier( aggregateColumn.getSqlType() ) ) == null;\n\t\t\t\tvalidateEqual( registeredUdt, udt );\n\t\t\t}\n\t\t}\n\t\telse {","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentSecondPass.java#L72-L108","documentation":"Hibernate maps an @Struct-annotated embeddable to a database user-defined type (UDT). During the AggregateComponentSecondPass it asks the dialect supportsUserDefinedTypes(); on dialects that report no UDT support (the MySQL/MariaDB family is typical) it throws this MappingException, explicitly telling you to remove the '@Struct' annotation.","triggerScenarios":"An @Embeddable class annotated with @Struct (or an aggregate mapping that resolves a struct name) is referenced by an entity while the configured dialect's supportsUserDefinedTypes() returns false - e.g. running the same entities on MySQL that were written for PostgreSQL.","commonSituations":"Portability testing across databases (PostgreSQL in prod, MySQL/H2 variants in CI); switching dialects without revisiting struct mappings; new @Struct usage added against the wrong database profile.","solutions":["Remove the @Struct annotation - keep the embeddable as a plain aggregate/JSON-mapped component","Or run against a database whose dialect supports user-defined types (PostgreSQL, Oracle, DB2)","Or remap the attribute to a JSON column (@JdbcTypeCode(SqlTypes.JSON)) if the DB has no UDTs","Verify you did not accidentally force a wrong dialect via hibernate.dialect or a wrong JDBC URL"],"exampleFix":"// before: struct on a UDT-less database (MySQL/MariaDB)\n@Embeddable\n@Struct(name = \"address\")\npublic class Address { ... }\n\n// after: plain aggregate mapped as JSON on such databases\n@Embeddable\npublic class Address { ... }\n\n@Entity\npublic class Customer {\n    @JdbcTypeCode(SqlTypes.JSON)\n    private Address address;      // no UDT required","handlingStrategy":"validation","validationCode":"// Before boot: check the dialect capability for every @Struct embeddable in the model\nstatic boolean structMappingsSupported(Dialect dialect) {\n    return dialect.supportsUserDefinedTypes();\n}\n\n// In multi-DB test suites, skip struct cases on incapable databases\nAssume.assumeTrue(structMappingsSupported(dialect));","typeGuard":null,"tryCatchPattern":"try {\n    sessionFactory = metadata.buildSessionFactory();\n} catch (MappingException e) {\n    if (e.getMessage().contains(\"remove '@Struct'\")) {\n        // database cannot host UDTs: fall back to a JSON/plain-embeddable variant of the model\n        throw new IllegalStateException(\"Model uses @Struct but DB lacks user-defined types\", e);\n    }\n    throw e;\n}","preventionTips":["Keep one mapping variant per database capability, selected by profile","Check dialect.supportsUserDefinedTypes() in a boot-time guard for models containing @Struct","Prefer JSON aggregate mappings when the deployment must stay portable across MySQL-like databases"],"tags":["hibernate","struct","udt","dialect","mapping","portability"],"backgroundTag":"user-defined-type-unsupported","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}