hibernate/hibernate-orm · error · MappingException
Secondary table (<join/>) must explicitly name table or sub-
Error message
Secondary table (<join/>) must explicitly name table or sub-select, but neither specified for entity [%s]
What it means
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.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/SecondaryTableSourceImpl.java:50
private final String logicalTableName;
private final List<ColumnSource> keyColumnSources;
public SecondaryTableSourceImpl(
MappingDocument sourceMappingDocument,
final JaxbHbmSecondaryTableType jaxbSecondaryTableMapping,
EntityNamingSource entityNamingSource,
Helper.InLineViewNameInferrer inLineViewNameInferrer) {
super( sourceMappingDocument );
this.jaxbSecondaryTableMapping = jaxbSecondaryTableMapping;
this.joinTable = Helper.createTableSource(
sourceMappingDocument,
jaxbSecondaryTableMapping,
inLineViewNameInferrer
);
if ( joinTable instanceof TableSource tableSource ) {
if ( isEmpty( tableSource.getExplicitTableName() ) ) {
throw new MappingException(
String.format(
Locale.ENGLISH,
"Secondary table (<join/>) must explicitly name table or sub-select, but neither " +
"specified for entity [%s]",
entityNamingSource.getEntityName()
),
sourceMappingDocument.getOrigin()
);
}
}
if ( joinTable instanceof TableSource tableSource ) {
logicalTableName = tableSource.getExplicitTableName();
}
else if ( joinTable instanceof InLineViewSource inLineViewSource ) {
logicalTableName = inLineViewSource.getLogicalName();
}
else {View on GitHub (pinned to fad1729dce)
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
Example fix
// before
<join fetch='join'>
<key column='id'/>
<property name='bio' column='bio'/>
</join>
// after
<join table='person_detail' fetch='join'>
<key column='person_id'/>
<property name='bio' column='bio'/>
</join> Defensive patterns
Strategy: validation
Validate before calling
// Pre-flight: every <join> must name a table or a subselect
var joins = doc.getElementsByTagName("join");
for (int i = 0; i < joins.getLength(); i++) {
var j = (org.w3c.dom.Element) joins.item(i);
if (j.getAttribute("table").isEmpty() && j.getAttribute("subselect").isEmpty())
throw new IllegalStateException("<join> without table or subselect under "
+ ((org.w3c.dom.Element) j.getParentNode()).getAttribute("name"));
} Try / catch
catch (org.hibernate.boot.MappingException e) at Metadata build: the message names the entity - open its <join> and add table='...' (or subselect='...')
Prevention
- 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
When it happens
Trigger: 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>.
Common situations: 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.
Related errors
- Encountered unexpected content type [%s] for named native qu
- <many-to-any /> mapping [%s] needs to specify 2 or more colu
- <many-to-any /> mapping [%s] needs to specify 2 or more colu
- Expecting just a single formula/column in context of <%s nam
- Expecting single column in context of <%s name="%s"/>, but f
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/2d20e883d2620c05.
Report an issue: GitHub.