{"record":{"id":"8ebe06503864feb0","repo":"hibernate/hibernate-orm","slug":"retrieved-key-was-null-but-to-one-is-not-nullable","errorCode":null,"errorMessage":"Retrieved key was null, but to-one is not nullable : %s","messagePattern":"Retrieved key was null, but to-one is not nullable : (.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/sql/exec/internal/lock/TableLock.java","lineNumber":314,"sourceCode":"\t\t\tapplyModelState( entityDetails, statePosition, stateValue );\n\t\t}\n\t}\n\n\t// Used by Hibernate Reactive\n\tprotected static class ToOneResultHandler extends AbstractResultHandler {\n\t\tprotected final ToOneAttributeMapping toOne;\n\n\t\tpublic ToOneResultHandler(Integer statePosition, ToOneAttributeMapping toOne) {\n\t\t\tsuper( statePosition );\n\t\t\tthis.toOne = toOne;\n\t\t}\n\n\t\t@Override\n\t\tpublic void applyResult(Object stateValue, EntityDetails entityDetails, SharedSessionContractImplementor session) {\n\t\t\tfinal Object reference;\n\t\t\tif ( stateValue == null ) {\n\t\t\t\tif ( !toOne.isNullable() ) {\n\t\t\t\t\tthrow new IllegalStateException( \"Retrieved key was null, but to-one is not nullable : \" + toOne.getNavigableRole().getFullPath() );\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\treference = null;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\treference = session.internalLoad(\n\t\t\t\t\t\ttoOne.getAssociatedEntityMappingType().getEntityName(),\n\t\t\t\t\t\tstateValue,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\ttoOne.isNullable()\n\t\t\t\t);\n\t\t\t}\n\t\t\tapplyLoadedState( entityDetails, statePosition, reference );\n\t\t\tapplyModelState( entityDetails, statePosition, reference );\n\t\t}\n\t}\n","sourceCodeStart":296,"sourceCodeEnd":332,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/lock/TableLock.java#L296-L332","documentation":"While applying follow-on table-lock results, TableLock.ToOneResultHandler reads the FK key value for a to-one association. If the database returned null but the mapping declares the association non-nullable (@ManyToOne(optional=false), @JoinColumn(nullable=false), or a mandatory FK), it throws IllegalStateException with the attribute's full navigable path. The mapping and the stored data disagree.","triggerScenarios":"A row has NULL in the FK column of a to-one mapped as optional=false, read while applying results of a pessimistic/follow-on table lock (session.lock, lock-mode queries on collections or associations).","commonSituations":"Legacy or manually imported data violating NOT NULL; the DB constraint missing or dropped so nulls entered; the mapping tightened (optional=true to false) after null data already existed; hbm2ddl/validation disabled so schema and mapping drifted.","solutions":["Fix the data: UPDATE the offending rows so the FK is populated (find them with 'where <fk_column> is null' on the mapped table)","Add/restore the NOT NULL constraint so violations cannot re-enter","If nulls are legitimate, correct the mapping: @ManyToOne(optional=true) / @JoinColumn(nullable=true)","During migration, exclude broken rows from the locking query until data is repaired"],"exampleFix":"// before\n@ManyToOne( optional = false )\n@JoinColumn( name = \"customer_id\", nullable = false )\nprivate Customer customer; // DB rows contain null customer_id\n\n// after (if nulls are legitimate)\n@ManyToOne( optional = true )\n@JoinColumn( name = \"customer_id\" )\nprivate Customer customer;\n-- or fix data: UPDATE Orders SET customer_id = :fallback WHERE customer_id IS NULL;","handlingStrategy":"validation","validationCode":"-- verify mapping matches data before enabling non-optional to-ones\nSELECT count(*) FROM child_table WHERE fk_column IS NULL;\n-- must return 0 for @ManyToOne(optional=false) mappings","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Run a null-check query on FK columns before tightening optional=false/nullable=false","Keep NOT NULL constraints in the schema so bad rows cannot enter","Enable schema validation (hibernate.hbm2ddl.auto=validate) to catch mapping/schema drift early"],"tags":["hibernate","orm","data-integrity","locking","many-to-one","illegal-state"],"backgroundTag":"non-nullable-column-null-data","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}