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

  1. Add @SecondaryTable(name = "X", ...) to the entity with exactly the name used in @Column(table = "X")
  2. Fix typos or letter-case differences between the two names (also check catalog/schema qualifiers)
  3. Ensure the @SecondaryTable is declared on the same entity (or properly inherited mapped superclass) that owns the property
  4. 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

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


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/ccebe8aac19ee336. Report an issue: GitHub.