{"record":{"id":"75e7d9dba477f7e7","repo":"hibernate/hibernate-orm","slug":"association-propertyname-marked-as-naturalid","errorCode":null,"errorMessage":"Association '{propertyName}' marked as '@NaturalId' is also annotated '@NotFound(IGNORE)'","messagePattern":"Association '(.+?)' marked as '@NaturalId' is also annotated '@NotFound\\(IGNORE\\)'","errorType":"exception","errorClass":"MappingException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/persister/entity/BaseEntityPersister.java","lineNumber":563,"sourceCode":"\t\t\t\tcollectionsInDefaultFetchGroupEnabled,\n\t\t\t\tcreationContext.getMetadata()\n\t\t);\n\t}\n\n\tprivate static boolean writePropertyValue(OnExecutionGenerator generator, EventType eventType) {\n\t\tfinal boolean writePropertyValue = generator.writePropertyValue( eventType );\n\t\t// TODO: move this validation somewhere else!\n//\t\tif ( !writePropertyValue && generator instanceof BeforeExecutionGenerator ) {\n//\t\t\tthrow new HibernateException( \"BeforeExecutionGenerator returned false from OnExecutionGenerator.writePropertyValue()\" );\n//\t\t}\n\t\treturn writePropertyValue;\n\t}\n\n\tprivate void verifyNaturalIdProperty(Property property) {\n\t\tfinal var value = property.getValue();\n\t\tif ( value instanceof ManyToOne toOne ) {\n\t\t\tif ( toOne.getNotFoundAction() == NotFoundAction.IGNORE ) {\n\t\t\t\tthrow new MappingException( \"Association '\" + propertyName( property )\n\t\t\t\t\t\t\t\t\t\t\t+ \"' marked as '@NaturalId' is also annotated '@NotFound(IGNORE)'\"\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\telse if ( value instanceof Component component ) {\n\t\t\tfor ( var componentProperty : component.getProperties() ) {\n\t\t\t\tverifyNaturalIdProperty( componentProperty );\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate String propertyName(Property property) {\n\t\treturn getName() + \".\" + property.getName();\n\t}\n\n\tprivate static Generator buildGenerator(\n\t\t\tfinal String entityName,\n\t\t\tfinal Property mappingProperty,","sourceCodeStart":545,"sourceCodeEnd":581,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/persister/entity/BaseEntityPersister.java#L545-L581","documentation":"BaseEntityPersister.verifyNaturalIdProperty inspects every property of the natural id, recursing into @Embedded components. A @NaturalId must be reliably resolvable, but @NotFound(action = IGNORE) makes a @ManyToOne tolerate a missing target row by loading null, which silently breaks natural-id resolution and caching, so the mapping is rejected with MappingException at boot. Because of the recursion, the combination inside an embeddable member of the natural id fails too.","triggerScenarios":"@ManyToOne @NaturalId @NotFound(action = NotFoundAction.IGNORE) on an association; the same combination nested inside an embeddable that is (part of) the natural id (component.getProperties() recursion).","commonSituations":"Retrofitting natural-id lookup onto legacy optional associations; using @NotFound(IGNORE) to tolerate broken or unenforced foreign keys; reusing an existing embeddable that already carried @NotFound as a natural-id component.","solutions":["Remove @NotFound(IGNORE) from the association (default is EXCEPTION) and fix the data/FK so targets exist","Drop @NaturalId from that association and choose a different natural-id property","If the association is genuinely optional it cannot be a natural id: remove it from the natural-id set or embeddable"],"exampleFix":"// before\n@ManyToOne(fetch = LAZY)\n@NaturalId\n@NotFound(action = NotFoundAction.IGNORE)\nprivate User owner;\n\n// after\n@ManyToOne(fetch = LAZY)\n@NaturalId\nprivate User owner;","handlingStrategy":"validation","validationCode":"static void checkNaturalIdMembers(Class<?> entity) {\n    for ( java.lang.reflect.Field f : entity.getDeclaredFields() ) {\n        if ( f.isAnnotationPresent(NaturalId.class)\n                && f.isAnnotationPresent(NotFound.class)\n                && f.getAnnotation(NotFound.class).action() == NotFoundAction.IGNORE ) {\n            throw new IllegalStateException(\"@NaturalId member '\" + f.getName()\n                + \"' must not combine @NotFound(IGNORE)\");\n        }\n    }\n    // recurse into embeddable members used in the natural id, the persister check does too\n}","typeGuard":null,"tryCatchPattern":"try {\n    sessionFactory = metadata.getSessionFactoryBuilder().build();\n}\ncatch ( org.hibernate.MappingException e ) {\n    throw new IllegalStateException(\"SessionFactory boot failed: \" + e.getMessage(), e);\n}","preventionTips":["Keep natural ids restricted to scalar fields and fully enforced (NOT NULL FK) associations","Avoid @NotFound(IGNORE) on new mappings; fix the data or the FK instead","When reusing embeddables in a natural id, check their members for @NotFound too"],"tags":["hibernate","orm","natural-id","mapping","associations","annotations"],"backgroundTag":"natural-id-mapping-conflict","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}