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

  1. 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.
  2. Join only attributes whose type is a ManagedDomainType: @ManyToOne/@OneToOne associations (ENTITY) and @Embedded components (EMBEDDABLE); verify with attribute.getType().getPersistenceType() before calling join.
  3. 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).
  4. 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

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


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