{"record":{"id":"bcfe495440fbe669","repo":"hibernate/hibernate-orm","slug":"non-terminal-property-path-did-not-reference-fetch-bcfe49","errorCode":null,"errorMessage":"Non-terminal property path did not reference FetchableContainer: \" + navigablePath","messagePattern":"Non-terminal property path did not reference FetchableContainer: \" \\+ navigablePath","errorType":"exception","errorClass":"MappingException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/boot/query/SqlResultSetMappingDescriptor.java","lineNumber":448,"sourceCode":"//\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic FetchMemento resolve(@Nonnull ResultSetMappingResolutionContext resolutionContext) {\n\t\t\tfinal var entityMapping =\n\t\t\t\t\tresolutionContext.getMappingMetamodel()\n\t\t\t\t\t\t\t.getEntityDescriptor( entityName );\n\t\t\tfinal String rootPropertyPathPart = propertyPathParts[0];\n\t\t\tvar subPart = entityMapping.findSubPart( rootPropertyPathPart, null );\n\t\t\tvar navigablePath = rootNavigablePath( subPart, entityMapping, rootPropertyPathPart );\n\t\t\tfor ( int i = 1; i < propertyPathParts.length; i++ ) {\n\t\t\t\tif ( subPart instanceof ModelPartContainer modelPartContainer ) {\n\t\t\t\t\tfinal String propertyPathPart = propertyPathParts[i];\n\t\t\t\t\tnavigablePath = navigablePath.append( propertyPathPart );\n\t\t\t\t\tsubPart = modelPartContainer.findSubPart( propertyPathPart, null );\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tthrow new MappingException(\n\t\t\t\t\t\t\t\"Non-terminal property path did not reference FetchableContainer: \"\n\t\t\t\t\t\t\t+ navigablePath\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn getFetchMemento( navigablePath, subPart );\n\t\t}\n\n\t\tprivate NavigablePath rootNavigablePath(\n\t\t\t\t@Nonnull ModelPart parentSubPart,\n\t\t\t\t@Nonnull EntityPersister entityMapping,\n\t\t\t\t@Nonnull String rootPropertyPathPart) {\n\t\t\tfinal var parentNavigableRole = parentSubPart.getNavigableRole().getParent();\n\t\t\tfinal var parentNavigablePath =\n\t\t\t\t\t!Objects.equals( parentNavigableRole, entityMapping.getNavigableRole() )\n\t\t\t\t\t\t&& parentNavigableRole.getLocalName().equals( ID_ROLE_NAME )\n\t\t\t\t\t\t\t// The attribute is defined in an ID class, append {id} to navigable path\n\t\t\t\t\t\t\t? new EntityIdentifierNavigablePath( this.navigablePath, null )","sourceCodeStart":430,"sourceCodeEnd":466,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/boot/query/SqlResultSetMappingDescriptor.java#L430-L466","documentation":"When resolving a @FieldResult name that contains multiple dot-separated parts, SqlResultSetMappingDescriptor walks the entity metamodel part by part. Each intermediate part must resolve to a ModelPartContainer (an embeddable, association, or collection) so the walk can continue; when an intermediate part is a basic attribute, the remaining parts have nowhere to go and this MappingException is thrown with the NavigablePath reached so far. It is the annotation-side twin of error 749.","triggerScenarios":"@FieldResult(name = \"address.zip.code\", column = \"ZIP\") where 'address.zip' is a plain String; a dotted path left over from when the attribute was an embeddable and is now basic; paths copied from another entity with a deeper component structure.","commonSituations":"Refactoring an embeddable into basic columns (or vice versa) without updating @SqlResultSetMapping; entity-specific mappings reused across similar entities; typos that make an intermediate segment resolve to a basic attribute.","solutions":["Shorten the path to end at the basic attribute: @FieldResult(name = \"address.zip\", column = \"ZIP\").","If nesting is intended, make the intermediate attribute an @Embeddable/component so it is a ModelPartContainer.","Verify each intermediate segment against the entity's current mapped structure and fix stale names."],"exampleFix":"// before: 'address.zip' is a basic String\n@FieldResult(name = \"address.zip.code\", column = \"ZIP\")\n\n// after\n@FieldResult(name = \"address.zip\", column = \"ZIP\")","handlingStrategy":"validation","validationCode":"// validate a dotted @FieldResult name against the metamodel before creating the EMF:\nManagedDomainType<?> t = metamodel.entity( entityClass );\nString[] parts = fieldResult.name().split( \"\\\\.\" );\nfor ( int i = 0; i < parts.length; i++ ) {\n    Attribute<?, ?> attr = (Attribute<?, ?>) ( (ManagedDomainType<?>) t ).getAttribute( parts[i] );\n    if ( attr == null ) throw new IllegalStateException( \"Unknown segment '\" + parts[i] + \"' in \" + fieldResult.name() );\n    if ( i < parts.length - 1 && ! ( attr.getPersistentAttributeType() == PersistentAttributeType.EMBEDDED\n            || attr.getPersistentAttributeType() == PersistentAttributeType.ELEMENT_COLLECTION\n            || attr.getPersistentAttributeType() == PersistentAttributeType.MANY_TO_ONE\n            || attr.getPersistentAttributeType() == PersistentAttributeType.ONE_TO_ONE ) ) {\n        throw new IllegalStateException( \"Segment '\" + parts[i] + \"' is basic; path too deep: \" + fieldResult.name() );\n    }\n    if ( i < parts.length - 1 ) t = metamodel.embeddable( attr.getJavaType() );\n}","typeGuard":null,"tryCatchPattern":"catch ( MappingException e ) {\n    if ( e.getMessage().startsWith( \"Non-terminal property path did not reference FetchableContainer\" ) ) {\n        // shorten the @FieldResult name to end at the basic attribute, or model the middle segment as an embeddable\n    }\n}","preventionTips":["Keep @FieldResult names in lockstep with the entity's component structure; update both when refactoring embeddables.","Smoke-test native queries with @SqlResultSetMapping in CI so structural drift is caught at build time."],"tags":["hibernate","jpa","sql-result-set-mapping","field-result","metamodel"],"backgroundTag":"non-terminal-property-path","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}