{"record":{"id":"ab82fa4c7330e29e","repo":"hibernate/hibernate-orm","slug":"the-from-clause-of-a-subquery-has-a-fetch","errorCode":null,"errorMessage":"The 'from' clause of a subquery has a 'fetch'","messagePattern":"The 'from' clause of a subquery has a 'fetch'","errorType":"exception","errorClass":"SemanticException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java","lineNumber":2287,"sourceCode":"\t}\n\n\tprivate JpaMetamodel getJpaMetamodel() {\n\t\treturn getCreationContext().getJpaMetamodel();\n\t}\n\n\t@Override\n\tpublic final SqmJoin<?, ?> visitJoin(HqlParser.JoinContext parserJoin) {\n\t\tthrow new UnsupportedOperationException( \"Unexpected call to #visitJoin, see #consumeJoin\" );\n\t}\n\n\tprotected <X> void consumeJoin(HqlParser.JoinContext parserJoin, SqmRoot<X> sqmRoot) {\n\t\tfinal var joinType = getSqmJoinType( parserJoin.joinType() );\n\t\tfinal var qualifiedJoinTargetContext = parserJoin.joinTarget();\n\t\tfinal String alias = extractAlias( getVariable( qualifiedJoinTargetContext ) );\n\t\tfinal boolean fetch = parserJoin.FETCH() != null;\n\n\t\tif ( fetch && processingStateStack.depth() > 1 ) {\n\t\t\tthrow new SemanticException( \"The 'from' clause of a subquery has a 'fetch'\", query );\n\t\t}\n\n\t\tfinal var joinRestrictionContext = parserJoin.joinRestriction();\n\t\t// Joins are allowed to be reused if they don't have a join condition\n\t\tfinal var allowReuse = joinRestrictionContext == null;\n\t\tdotIdentifierConsumerStack.push( new QualifiedJoinPathConsumer( sqmRoot, joinType, fetch, alias, allowReuse, this ) );\n\t\ttry {\n\t\t\tfinal var join = getJoin( sqmRoot, joinType, qualifiedJoinTargetContext, alias, fetch );\n\t\t\tif ( join instanceof SqmEntityJoin<?,?> || join instanceof SqmDerivedJoin<?> || join instanceof SqmCteJoin<?> ) {\n\t\t\t\tsqmRoot.addSqmJoin( join );\n\t\t\t}\n\t\t\telse if ( join instanceof SqmAttributeJoin<?, ?> attributeJoin ) {\n\t\t\t\tif ( getCreationOptions().useStrictJpaCompliance() ) {\n\t\t\t\t\tif ( join.getExplicitAlias() != null && attributeJoin.isFetched() ) {\n\t\t\t\t\t\tthrow new StrictJpaComplianceViolation(\n\t\t\t\t\t\t\t\t\"Encountered aliased fetch join, but strict JPQL compliance was requested\",\n\t\t\t\t\t\t\t\tStrictJpaComplianceViolation.Type.ALIASED_FETCH_JOIN\n\t\t\t\t\t\t);","sourceCodeStart":2269,"sourceCodeEnd":2305,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java#L2269-L2305","documentation":"consumeJoin throws when the 'fetch' keyword appears on a join inside a subquery (processingStateStack.depth() > 1). Fetching is only defined for the outermost query: a subquery produces intermediate results, not managed results whose associations could be initialized, so 'fetch' in a nested from clause is a semantic error.","triggerScenarios":"'select e from Employee e where exists (select 1 from e.items i join fetch i.subTasks st)'; any join marked 'fetch' inside a subquery from clause.","commonSituations":"Copying an outer fetch join into a subquery while refactoring an exists/in predicate; ORMs/query DSLs that append 'fetch' to every join uniformly.","solutions":["Remove 'fetch' from the join inside the subquery","Move the fetch join to the outer query: 'select e from Employee e left join fetch e.items'","Use a plain join in the subquery - fetching inside it has no effect anyway"],"exampleFix":"// before\nselect e from Employee e\nwhere exists (select 1 from e.items i join fetch i.subTasks st)\n\n// after\nselect e from Employee e left join fetch e.items i\nwhere exists (select 1 from Employee e2 where e2.id = e.id and size(e2.items) > 0)","handlingStrategy":"validation","validationCode":"// Lint generated HQL: 'fetch' must not appear inside a subquery's from clause\nstatic boolean fetchInsideSubquery(String hql) {\n    java.util.regex.Matcher m = java.util.regex.Pattern.compile(\"\\\\((?:select\\\\s+).*?\\\\)\", java.util.regex.Pattern.CASE_INSENSITIVE | java.util.regex.Pattern.DOTALL).matcher(hql);\n    while (m.find()) {\n        if (m.group().toLowerCase().contains(\" join fetch \")) return true;\n    }\n    return false;\n}","typeGuard":null,"tryCatchPattern":"try {\n    return em.createQuery(hql, Employee.class).getResultList();\n} catch (org.hibernate.query.sqm.SemanticException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"subquery has a 'fetch'\")) {\n        throw new IllegalArgumentException(\"Remove 'fetch' from joins inside subqueries; fetch in the outer query instead\", e);\n    }\n    throw e;\n}","preventionTips":["Reserve 'join fetch' for the outermost query only","When refactoring an exists/in predicate out of a main query, strip fetch keywords from the moved joins","Fetching inside a subquery has no effect anyway - plain joins are always correct there"],"tags":["hql","fetch-join","subquery","hibernate"],"backgroundTag":"fetch-join-in-subquery","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}