hibernate/hibernate-orm · error · MappingException
@TenantId attribute must be mapped to a single column or for
Error message
@TenantId attribute must be mapped to a single column or formula
What it means
TenantIdBinder builds the tenant filter condition from exactly one selectable of the @TenantId property (a single column or a single formula). If the property maps to zero or multiple columns — a composite/embedded tenant id — property.getColumnSpan() != 1 and binding throws a MappingException.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/binder/internal/TenantIdBinder.java:139
hasTenantCredentialsMapper( buildingContext )
&& rowLevelSecurity.supportsTenantIdentifierSource( TenantIdentifierSource.DATABASE_USER )
? TenantIdentifierSource.DATABASE_USER
: TenantIdentifierSource.SESSION
);
}
}
}
private static boolean hasTenantCredentialsMapper(MetadataBuildingContext buildingContext) {
final var bootstrapContext = buildingContext.getBootstrapContext();
final var settings = bootstrapContext.getConfigurationService().getSettings();
return settings.get( MULTI_TENANT_CREDENTIALS_MAPPER ) != null
|| getTenantCredentialsMapper( settings, bootstrapContext.getServiceRegistry() ) != null;
}
private String columnNameOrFormula(Property property) {
if ( property.getColumnSpan() != 1 ) {
throw new MappingException( "@TenantId attribute must be mapped to a single column or formula" );
}
final var selectable = property.getSelectables().get( 0 );
if ( selectable instanceof Formula formula ) {
return formula.getFormula();
}
else if ( selectable instanceof Column column ) {
return column.getName();
}
else {
throw new AssertionFailure( "@TenantId attribute must be mapped to a column or formula" );
}
}
}
View on GitHub (pinned to fad1729dce)
Solutions
- Map the tenant discriminator as a single basic column or a single formula.
- Combine multi-part tenancy into one column/formula, or go back to explicit @FilterDef/@Filter for composite tenant matching.
- Never annotate fields inside @EmbeddedId or embeddable classes with @TenantId.
Example fix
// before
@Embeddable public class TenantKey { String group; String region; }
@Entity public class Doc {
@EmbeddedId @TenantId TenantKey id;
}
// after
@Entity public class Doc {
@Id long id;
@TenantId String tenantId; // single column
} Defensive patterns
Strategy: validation
Validate before calling
for (Class<?> cls : embeddableClasses) {
for (Field f : cls.getDeclaredFields()) {
if (f.isAnnotationPresent(org.hibernate.annotations.TenantId.class)) {
throw new IllegalStateException("@TenantId inside embeddable/composite key " + cls.getName());
}
}
} Try / catch
Catch org.hibernate.MappingException during bootstrap; the message identifies the @TenantId mapping. Fix the mapping to a single column or formula.
Prevention
- Model the tenant discriminator as one basic column.
- Never place @TenantId inside @EmbeddedId or composite types.
- Use @FilterDef/@Filter when composite tenant matching is unavoidable.
When it happens
Trigger: @TenantId placed on a property mapped to more than one column: an @EmbeddedId component, a composite user type, or an attribute spanning several columns via @Columns.
Common situations: Composite tenancy keys (tenant_id + region) required by legacy schemas; putting @TenantId on a field of an @EmbeddedId class; migrating from hand-written @Filter SQL (which allowed arbitrary expressions) to @TenantId.
Related errors
- all @TenantId fields must have the same type: <parameterType
- Association '%s' is 'mappedBy' a property '%s' of entity '%s
- assigned tenant id differs from current tenant id [{} != {}]
- SessionFactory configured for multi-tenancy, but no tenant i
- Row-level security enabled, but no tenant identifier specifi
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/e4a3211186207e7d.
Report an issue: GitHub.