hibernate/hibernate-orm · error · SemanticException

Fetch join has a 'with' clause (use a filter instead)

Error message

Fetch join has a 'with' clause (use a filter instead)

What it means

consumeJoin rejects a fetch join that declares a 'with' restriction ('join fetch e.tasks t with t.done = true'). A conditional fetch join would build partially initialized collections and break fetch semantics, so Hibernate throws SemanticException and points to filters as the supported way to restrict fetched associations.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java:2309

		// Joins are allowed to be reused if they don't have a join condition
		final var allowReuse = joinRestrictionContext == null;
		dotIdentifierConsumerStack.push( new QualifiedJoinPathConsumer( sqmRoot, joinType, fetch, alias, allowReuse, this ) );
		try {
			final var join = getJoin( sqmRoot, joinType, qualifiedJoinTargetContext, alias, fetch );
			if ( join instanceof SqmEntityJoin<?,?> || join instanceof SqmDerivedJoin<?> || join instanceof SqmCteJoin<?> ) {
				sqmRoot.addSqmJoin( join );
			}
			else if ( join instanceof SqmAttributeJoin<?, ?> attributeJoin ) {
				if ( getCreationOptions().useStrictJpaCompliance() ) {
					if ( join.getExplicitAlias() != null && attributeJoin.isFetched() ) {
						throw new StrictJpaComplianceViolation(
								"Encountered aliased fetch join, but strict JPQL compliance was requested",
								StrictJpaComplianceViolation.Type.ALIASED_FETCH_JOIN
						);
					}
				}
				if ( joinRestrictionContext != null && attributeJoin.isFetched() ) {
					throw new SemanticException( "Fetch join has a 'with' clause (use a filter instead)", query );
				}
			}

			if ( joinRestrictionContext != null ) {
				dotIdentifierConsumerStack.push( new QualifiedJoinPredicatePathConsumer( join, this ) );
				try {
					join.setJoinPredicate( (SqmPredicate) joinRestrictionContext.getChild( 1 ).accept( this ) );
				}
				finally {
					dotIdentifierConsumerStack.pop();
				}
			}
		}
		finally {
			dotIdentifierConsumerStack.pop();
		}
	}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Use a plain (non-fetch) join with the condition if you only need filtered tuples: 'join e.tasks t where t.active = true'
  2. Apply @FilterDef/@Filter on the association so fetched rows are restricted consistently, then fetch without 'with'
  3. Model the restricted set as a separate association or query it separately to keep fetch semantics intact

Example fix

// before
select e from Employee e join fetch e.tasks t with t.active = true

// after (filtered tuples, no eager collection init)
select e from Employee e join e.tasks t where t.active = true

// or restrict the association via @Filter and fetch plainly
select e from Employee e join fetch e.tasks
Defensive patterns

Strategy: validation

Validate before calling

// Reject 'join fetch ... with ...' before execution
static boolean conditionalFetchJoin(String hql) {
    return java.util.regex.Pattern.compile("join\\s+fetch\\s+[^;]+?\\s+with\\s", java.util.regex.Pattern.CASE_INSENSITIVE | java.util.regex.Pattern.DOTALL).matcher(hql).find();
}
if (conditionalFetchJoin(hql)) throw new IllegalArgumentException("Fetch joins cannot have 'with' conditions - use filters or a plain join");

Try / catch

try {
    return em.createQuery(hql, Employee.class).getResultList();
} catch (org.hibernate.query.sqm.SemanticException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Fetch join has a 'with' clause")) {
        throw new IllegalArgumentException("Split into a plain join for filtering, or use @Filter on the association", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: 'select e from Employee e join fetch e.tasks t with t.active = true'; any 'join fetch ... with <predicate>' in HQL.

Common situations: Trying to load only part of a collection eagerly (the classic XY problem); porting native SQL left joins with conditions into fetch joins; upgrading Hibernate where older versions tolerated some forms.

Related errors


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