hibernate/hibernate-orm · error · SemanticException
Attribute '{attribute}' is not joinable
Error message
Attribute '{attribute}' is not joinable What it means
Thrown by AbstractSqmFrom#buildSingularJoin (hibernate-core .../tree/spi/domain/AbstractSqmFrom.java:983) when a query join is built over a singular attribute whose type is not a ManagedDomainType — i.e. anything that is not an entity, embeddable, or mapped superclass (a basic String/Integer/enum attribute, a basic array, or an @Any mapping, whose AnyMappingDomainType is only a SimpleDomainType). SQL joins need a managed type on the right-hand side so Hibernate can build a navigable path across the association; basic values have nothing to join to. It is a SemanticException raised while the SQM tree is built, so the query fails before any SQL is generated or executed.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/tree/spi/domain/AbstractSqmFrom.java:983
}
}
private <A> SqmSingularJoin<T, A> buildSingularJoin(
SqmSingularPersistentAttribute<? super T, A> attribute,
SqmJoinType joinType,
boolean fetched) {
if ( attribute.getPathType() instanceof ManagedDomainType ) {
return new SqmSingularJoin<>(
this,
attribute,
generateAlias(),
joinType,
fetched,
nodeBuilder()
);
}
throw new SemanticException( "Attribute '" + attribute + "' is not joinable" );
}
private <E> SqmBagJoin<T, E> buildBagJoin(
BagPersistentAttribute<? super T, E> attribute,
SqmJoinType joinType,
boolean fetched) {
return new SqmBagJoin<>(
this,
(SqmBagPersistentAttribute<? super T, E>) attribute,
generateAlias(),
joinType,
fetched,
nodeBuilder()
);
}
private <E> SqmListJoin<T, E> buildListJoin(
ListPersistentAttribute<? super T, E> attribute,View on GitHub (pinned to fad1729dce)
Solutions
- Replace the join with a predicate: basic attributes are filtered, not joined — use cb.equal(root.get(Order_.status), value) in criteria or a plain comparison in HQL.
- Join only attributes whose type is a ManagedDomainType: @ManyToOne/@OneToOne associations (ENTITY) and @Embedded components (EMBEDDABLE); verify with attribute.getType().getPersistenceType() before calling join.
- If the association was intended, fix the attribute reference (Order_.customer, not Order_.customerName) or the HQL path (join o.customer, not join o.customerName).
- For @Any attributes, never join — filter on the discriminator/meta columns and load the target entity separately by id.
Example fix
// before
SqmRoot<Order> order = query.from(Order.class);
order.join("status"); // status is String -> SemanticException: Attribute 'status' is not joinable
// after
SqmRoot<Order> order = query.from(Order.class);
query.where(cb.equal(order.get("status"), "OPEN")); // basic attributes are compared, not joined Defensive patterns
Strategy: validation
Validate before calling
import jakarta.persistence.metamodel.*;
SingularAttribute<? super Order, ?> attr = Order_.status; // example attribute
Type<?> t = attr.getType();
boolean joinable = t.getPersistenceType() == PersistenceType.ENTITY
|| t.getPersistenceType() == PersistenceType.EMBEDDABLE;
if (!joinable) {
throw new IllegalArgumentException(
"Attribute '" + attr.getName() + "' is basic-valued; filter with a predicate instead of joining");
}
root.join(attr, JoinType.INNER); Type guard
static boolean isJoinableAttribute(SingularAttribute<?, ?> attr) {
PersistenceType p = attr.getType().getPersistenceType();
return p == PersistenceType.ENTITY || p == PersistenceType.EMBEDDABLE;
} Try / catch
try {
root.join(attr, JoinType.INNER);
} catch (org.hibernate.query.SemanticException e) {
// query-construction error: report the offending attribute, do not retry
throw new BadRequestException("Cannot join attribute: " + e.getMessage(), e);
} Prevention
- Only join associations (@ManyToOne/@OneToOne) and embeddables; never column/basic attributes.
- In dynamic query DSLs, whitelist joinable attribute names against the JPA metamodel before building joins.
- Write joins against metamodel constants (Order_.customer) so the compiler catches wrong attributes.
- Remember @Any and @ManyToAny attributes are never joinable — plan discriminator-based queries instead.
When it happens
Trigger: Calling Root.join(String) / join(String, JoinType) / join(SingularAttribute) on a basic-typed attribute, e.g. root.join(Order_.status) where status is a String — all these AbstractSqmFrom.join overloads funnel into buildSingularJoin (AbstractSqmFrom.java:968). The same code path is reached from an explicit HQL join such as 'select o from Order o join o.status s'. Also triggered by joining an @Any-mapped attribute, or by a dynamic query builder that joins any attribute name it receives.
Common situations: Dynamic query DSLs that turn every client-supplied field name into a join; metamodel constant typos (Order_.customerName instead of Order_.customer); copy-pasting an HQL join onto a column instead of an association; expecting @Any/@ManyToAny attributes to behave like joinable polymorphic associations; queries migrated from older Hibernate versions that produced different diagnostics.
Related errors
- Could not resolve attribute '%s' of '%s'
- Index operator applied to non-plural path '${getNavigablePat
- Setting a predicate for a plural part join is unsupported
- NATIVE is not a legal field for extract()
- Inverse distribution function '%s' must specify 'WITHIN GROU
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/3eff8261b31301c4.
Report an issue: GitHub.