{"record":{"id":"2d20e883d2620c05","repo":"hibernate/hibernate-orm","slug":"secondary-table-join-must-explicitly-name-tab","errorCode":null,"errorMessage":"Secondary table (<join/>) must explicitly name table or sub-select, but neither specified for entity [%s]","messagePattern":"Secondary table \\(<join/>\\) must explicitly name table or sub-select, but neither specified for entity \\[(.+?)\\]","errorType":"exception","errorClass":"MappingException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/SecondaryTableSourceImpl.java","lineNumber":50,"sourceCode":"\tprivate final String logicalTableName;\n\tprivate final List<ColumnSource> keyColumnSources;\n\n\tpublic SecondaryTableSourceImpl(\n\t\t\tMappingDocument sourceMappingDocument,\n\t\t\tfinal JaxbHbmSecondaryTableType jaxbSecondaryTableMapping,\n\t\t\tEntityNamingSource entityNamingSource,\n\t\t\tHelper.InLineViewNameInferrer inLineViewNameInferrer) {\n\t\tsuper( sourceMappingDocument );\n\t\tthis.jaxbSecondaryTableMapping = jaxbSecondaryTableMapping;\n\t\tthis.joinTable = Helper.createTableSource(\n\t\t\t\tsourceMappingDocument,\n\t\t\t\tjaxbSecondaryTableMapping,\n\t\t\t\tinLineViewNameInferrer\n\t\t);\n\n\t\tif ( joinTable instanceof TableSource tableSource ) {\n\t\t\tif ( isEmpty( tableSource.getExplicitTableName() ) ) {\n\t\t\t\tthrow new MappingException(\n\t\t\t\t\t\tString.format(\n\t\t\t\t\t\t\t\tLocale.ENGLISH,\n\t\t\t\t\t\t\t\t\"Secondary table (<join/>) must explicitly name table or sub-select, but neither \" +\n\t\t\t\t\t\t\t\t\t\t\"specified for entity [%s]\",\n\t\t\t\t\t\t\t\tentityNamingSource.getEntityName()\n\t\t\t\t\t\t),\n\t\t\t\t\t\tsourceMappingDocument.getOrigin()\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif ( joinTable instanceof TableSource tableSource ) {\n\t\t\tlogicalTableName = tableSource.getExplicitTableName();\n\t\t}\n\t\telse if ( joinTable instanceof InLineViewSource inLineViewSource ) {\n\t\t\tlogicalTableName = inLineViewSource.getLogicalName();\n\t\t}\n\t\telse {","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/SecondaryTableSourceImpl.java#L32-L68","documentation":"An hbm <join> maps a secondary table and must identify it either by an explicit table name (table attribute) or by a sub-select (subselect attribute). Helper.createTableSource returns a TableSource for named tables; when the explicit table name comes back empty - neither table= nor subselect= was supplied - bootstrap fails naming the entity whose <join> is incomplete.","triggerScenarios":"A <join> element under a <class> or <joined-subclass> without a table attribute and without a subselect, e.g. <join fetch='join'><key column='id'/></join>.","commonSituations":"Dropping the table attribute during refactoring; expecting Hibernate to infer the secondary table name from the entity (it never does for <join>); attribute renamed or mistyped while editing.","solutions":["Add table='secondary_table' naming the physical secondary table to the <join> element","Or supply subselect='select ...' when the 'table' is actually a derived view","Afterwards verify the <key> column(s) that link the secondary table to the primary"],"exampleFix":"// before\n<join fetch='join'>\n    <key column='id'/>\n    <property name='bio' column='bio'/>\n</join>\n\n// after\n<join table='person_detail' fetch='join'>\n    <key column='person_id'/>\n    <property name='bio' column='bio'/>\n</join>","handlingStrategy":"validation","validationCode":"// Pre-flight: every <join> must name a table or a subselect\nvar joins = doc.getElementsByTagName(\"join\");\nfor (int i = 0; i < joins.getLength(); i++) {\n    var j = (org.w3c.dom.Element) joins.item(i);\n    if (j.getAttribute(\"table\").isEmpty() && j.getAttribute(\"subselect\").isEmpty())\n        throw new IllegalStateException(\"<join> without table or subselect under \"\n                + ((org.w3c.dom.Element) j.getParentNode()).getAttribute(\"name\"));\n}","typeGuard":null,"tryCatchPattern":"catch (org.hibernate.boot.MappingException e) at Metadata build: the message names the entity - open its <join> and add table='...' (or subselect='...')","preventionTips":["Never rely on inference for secondary tables - always set table= on <join>","After renaming secondary tables in DDL, update every <join table=...> in the mappings","Bootstrap the SessionFactory in tests so a missing table name fails the build"],"tags":["hibernate","hbm","secondary-table","join","mapping","orm"],"backgroundTag":"missing-table-name","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}