hibernate/hibernate-orm · error · AnnotationException
Secondary table '${explicitTableName}' for property '${prope
Error message
Secondary table '${explicitTableName}' for property '${propertyName}' of entity'${className}' is not declared (use '@SecondaryTable' to declare the secondary table) What it means
When a column declares @Column(table = "..."), AnnotatedColumns.getJoin() resolves the secondary table by that explicit name from the joins map built from the entity's @SecondaryTable declarations. If no declared secondary table matches, binding throws this AnnotationException naming the table, the property, and the entity, and suggesting '@SecondaryTable'.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumns.java:89
public void setBuildingContext(MetadataBuildingContext buildingContext) {
this.buildingContext = buildingContext;
}
public MetadataBuildingContext getBuildingContext() {
return buildingContext;
}
public void setJoins(Map<String, Join> joins) {
this.joins = joins;
}
public Join getJoin() {
final var firstColumn = columns.get( 0 );
final String explicitTableName = firstColumn.getExplicitTableName();
//note: checkPropertyConsistency() is responsible for ensuring they all have the same table name
final var join = getJoin( explicitTableName );
if ( join == null ) {
throw new AnnotationException(
"Secondary table '" + explicitTableName + "' for property '" + propertyName + "' of entity'" + getPropertyHolder().getClassName()
+ "' is not declared (use '@SecondaryTable' to declare the secondary table)"
);
}
else {
return join;
}
}
private Join getJoin(String explicitTableName) {
final var join = joins.get( explicitTableName );
if ( join != null ) {
return join;
}
else {
// annotation binding seems to use logical and physical naming somewhat inconsistently...
final String physicalTableName =
getBuildingContext().getMetadataCollector()View on GitHub (pinned to fad1729dce)
Solutions
- Add @SecondaryTable(name = "X", ...) to the entity with exactly the name used in @Column(table = "X")
- Fix typos or letter-case differences between the two names (also check catalog/schema qualifiers)
- Ensure the @SecondaryTable is declared on the same entity (or properly inherited mapped superclass) that owns the property
- Verify pkJoinColumn/joinColumn setup once the table resolves, since that is checked next
Example fix
// before: secondary table used but never declared
@Entity
public class User {
@Id private Long id;
@Column(table = "user_audit") // not declared -> error
private String lastModifiedBy;
}
// after: declare the secondary table on the entity
@Entity
@SecondaryTable(name = "user_audit",
pkJoinColumns = @PrimaryKeyJoinColumn(name = "user_id"))
public class User {
@Id private Long id;
@Column(table = "user_audit")
private String lastModifiedBy;
} Defensive patterns
Strategy: validation
Validate before calling
// Before boot: every @Column(table=...) name must be a declared @SecondaryTable name
static boolean secondaryTableReferencesValid(Class<?> entity) {
Set<String> declared = new HashSet<>();
SecondaryTable st = entity.getAnnotation(SecondaryTable.class);
if (st != null) declared.add(st.name());
SecondaryTables sts = entity.getAnnotation(SecondaryTables.class);
if (sts != null) Arrays.stream(sts.value()).forEach(t -> declared.add(t.name()));
for (Field f : entity.getDeclaredFields()) {
Column c = f.getAnnotation(Column.class);
if (c != null && !c.table().isEmpty() && !declared.contains(c.table())) return false;
}
return true;
} Try / catch
try {
sessionFactory = metadata.buildSessionFactory();
} catch (AnnotationException e) {
if (e.getMessage().contains("is not declared (use '@SecondaryTable'")) {
throw new IllegalStateException("@Column(table=...) references an undeclared secondary table", e);
}
throw e;
} Prevention
- Declare every secondary table on the entity before referencing it from @Column
- Keep table names in constants to avoid drift between declaration and usage
- When renaming a secondary table, search for all @Column(table = ...) references
When it happens
Trigger: @Column(table = "X") on a property of an entity where X is not declared via @SecondaryTable on that entity - a typo, different case, the declaration sitting on another entity in a @MappedSuperclass chain that does not propagate, or simply a forgotten declaration.
Common situations: Forgetting @SecondaryTable when moving columns to a side table; renaming a table in one place; copy-pasting entities with secondary-table columns; assuming @SecondaryTable from a superclass is inherited when it is not visible to this mapping.
Related errors
- '@AttributeAccessor' annotation must specify a 'strategy'
- Attribute was not a Map : ${collectionMemberType}
- Unable to create AttributeConverter instance
- Write expression in '@ColumnTransformer' for property '${pro
- Cannot perform #forceNotNull because internal org.hibernate.
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/ccebe8aac19ee336.
Report an issue: GitHub.