hibernate/hibernate-orm · error · IllegalStateException

Requested join fetch with association [%s] with '%s' join ty

Error message

Requested join fetch with association [%s] with '%s' join type, but found existing join fetch with '%s' join type.

What it means

Thrown as IllegalStateException by SqmUtil.findCompatibleFetchJoin when the same association is already join-fetched but with a different SqmJoinType than the one now requested. When resolving a fetch of a path, Hibernate reuses an existing fetched attribute join only if the join type matches; a mismatch is treated as a user error because two fetches of one association with different join types have contradictory semantics (an inner fetch cannot coexist with a left fetch of the same relation).

Source

Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java:454

			}
			else {
				expression.accept( pathVisitor );
			}
		}
		return navigablePaths;
	}

	public static <T, A> SqmAttributeJoin<T, A> findCompatibleFetchJoin(
			SqmFrom<?, T> sqmFrom,
			SqmPathSource<A> pathSource,
			SqmJoinType requestedJoinType) {
		for ( final var join : sqmFrom.getSqmJoins() ) {
			if ( join.getModel() == pathSource ) {
				final var attributeJoin = (SqmAttributeJoin<T, ?>) join;
				if ( attributeJoin.isFetched() ) {
					final var joinType = join.getSqmJoinType();
					if ( joinType != requestedJoinType ) {
						throw new IllegalStateException( String.format(
								"Requested join fetch with association [%s] with '%s' join type, " +
										"but found existing join fetch with '%s' join type.",
								pathSource.getPathName(),
								requestedJoinType,
								joinType
						) );
					}
					//noinspection unchecked
					return (SqmAttributeJoin<T, A>) attributeJoin;
				}
			}
		}
		return null;
	}

	public static Map<QueryParameterImplementor<?>, Map<SqmParameter<?>, List<JdbcParametersList>>> generateJdbcParamsXref(
			DomainParameterXref domainParameterXref,
			JdbcParameterBySqmParameterAccess jdbcParameterBySqmParameterAccess) {

View on GitHub (pinned to fad1729dce)

Solutions

  1. Make the join types identical for every fetch of the same association (use 'left join fetch' both times, or 'join fetch' both times)
  2. Remove the duplicate fetch — a single fetch of the association already loads it
  3. In Criteria, reuse the existing Fetch object instead of calling fetch(attribute, joinType) again for the same attribute
  4. Audit @EntityGraph / applied graphs for a conflicting fetch of the same association and align its join type

Example fix

// before
String hql = "select p from Person p join fetch p.address left join fetch p.address";
// after
String hql = "select p from Person p left join fetch p.address";
Defensive patterns

Strategy: validation

Validate before calling

static void assertNoConflictingFetchJoins(FetchParent<?, ?> parent) {
    Map<String, JoinType> seen = new HashMap<>();
    for (Fetch<?, ?> f : parent.getFetches()) {
        JoinType prev = seen.put(f.getAttribute().getName(), f.getJoinType());
        if (prev != null && prev != f.getJoinType()) {
            throw new IllegalStateException(
                "Conflicting join fetch types for " + f.getAttribute().getName());
        }
    }
}

Try / catch

try {
    return session.createQuery(hql, Person.class).getResultList();
} catch (IllegalStateException e) {
    if (e.getMessage() != null && e.getMessage().contains("existing join fetch")) {
        // two fetches of one association with different join types: fix the query string
        throw new IllegalArgumentException("Conflicting fetch join types in query: " + hql, e);
    }
    throw e;
}

Prevention

When it happens

Trigger: HQL that fetches the same association twice with different join types: 'select p from Person p left join fetch p.address join fetch p.address'; Criteria code calling root.fetch("address", JoinType.LEFT) and later root.fetch("address", JoinType.INNER); query builders or @EntityGraph handling that add a default inner 'join fetch' on top of an existing 'left join fetch' of the same path.

Common situations: One team fixes an N+1 with 'join fetch' while another layer (entity graph, repository fragment, generated query) already declared 'left join fetch' on the same association; upgrades that change default fetch join generation; criteria utilities that always apply fetches with INNER unless told otherwise.

Related errors


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