{"record":{"id":"5a8ccd79c8baa1ad","repo":"hibernate/hibernate-orm","slug":"null-id-generated-for-entity-s","errorCode":null,"errorMessage":"Null id generated for entity '%s'","messagePattern":"Null id generated for entity '(.+?)'","errorType":"exception","errorClass":"IdentifierGenerationException","httpStatus":null,"severity":"critical","filePath":"hibernate-core/src/main/java/org/hibernate/action/queue/internal/decompose/entity/PostInsertHandling.java","lineNumber":153,"sourceCode":"\t\t}\n\n\t\tObject generatedId = generatedValues.getGeneratedValue( persister.getIdentifierMapping() );\n\t\tif ( generatedId == null ) {\n\t\t\tgeneratedId = persister.getIdentifier( entity, session );\n\t\t}\n\t\tif ( generatedId != null ) {\n\t\t\tidentifierHandle.set( generatedId );\n\t\t\tpersister.setIdentifier( entity, generatedId, session );\n\t\t\tif ( action instanceof EntityIdentityInsertAction identityInsertAction ) {\n\t\t\t\tidentityInsertAction.setGeneratedId( generatedId );\n\t\t\t\tfinal var entityKey = session.generateEntityKey( generatedId, persister );\n\t\t\t\tidentityInsertAction.setEntityKey( entityKey );\n\t\t\t\tsession.getPersistenceContextInternal().checkUniqueness( entityKey, entity );\n\t\t\t}\n\t\t\treturn generatedId;\n\t\t}\n\t\telse {\n\t\t\tthrow new IdentifierGenerationException(\n\t\t\t\t\t\"Null id generated for entity '\" + persister.getEntityName() + \"'\"\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate void handleGeneratedProperties(\n\t\t\tObject id,\n\t\t\tEntityEntry entry,\n\t\t\tGeneratedValues generatedValues,\n\t\t\tPersistenceContext persistenceContext,\n\t\t\tEntityPersister persister,\n\t\t\tObject[] state) {\n\n\t\tif ( persister.hasInsertGeneratedProperties() ) {\n\t\t\tfinal Object instance = action.getInstance();\n\t\t\tpersister.processInsertGeneratedProperties(\n\t\t\t\t\tid,\n\t\t\t\t\tinstance,","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/action/queue/internal/decompose/entity/PostInsertHandling.java#L135-L171","documentation":"PostInsertHandling (PostInsertHandling.java:153) applies the identifier generated by the database after an insert: if the retrieved generated id is null it throws IdentifierGenerationException(\"Null id generated for entity '<entity>'\"). The INSERT executed but the JDBC driver/dialect returned no generated key, or the generator produced null - so Hibernate cannot complete the insert bookkeeping and the flush aborts. Common culprits are GenerationType.IDENTITY on a column that is not actually auto-increment, or a driver/trigger combination that breaks getGeneratedKeys/RETURNING.","triggerScenarios":"strategy = GenerationType.IDENTITY but the physical column lacks AUTO_INCREMENT/IDENTITY/SERIAL (DDL drift between environments); insert triggers (Oracle trigger-based id simulation, SQL Server INSTEAD OF triggers) that make getGeneratedKeys return null; custom or dialect-specific generators returning null; mismatched dialect for the actual database.","commonSituations":"Schema managed by hand or by a different tool (Liquibase/Flyway script forgot AUTO_INCREMENT); Oracle/SQL Server with trigger-based sequences; pointing a dialect for the wrong database version; upgrading the JDBC driver and generated-keys behavior changes; test containers using a different DB engine than production.","solutions":["Verify the table DDL actually auto-generates the id (AUTO_INCREMENT / IDENTITY / SERIAL / GENERATED AS IDENTITY) and migrate the schema if it drifted","Prefer GenerationType.SEQUENCE with an allocated sequence (or UUID) over IDENTITY - it does not depend on getGeneratedKeys behavior","On Oracle with trigger-based ids, move to identity columns or sequences with the proper dialect, and remove the trigger workaround","Check the JDBC driver matches the database version and the Hibernate dialect matches the database - then retest insert+flush in isolation"],"exampleFix":"// before - IDENTITY strategy but the column is a plain INT (no auto-increment)\n@Id\n@GeneratedValue(strategy = GenerationType.IDENTITY)\nprivate Long id;            // insert succeeds, generated key comes back null\n\n// after - use a sequence that Hibernate controls\n@Id\n@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = \"person_seq\")\n@SequenceGenerator(name = \"person_seq\", sequenceName = \"person_seq\", allocationSize = 50)\nprivate Long id;","handlingStrategy":"validation","validationCode":"// after persist/flush of a generated-id entity, verify the id materialized\nem.persist(entity);\nem.flush();\nif (em.getIdentifier(entity) == null) {\n    throw new IllegalStateException(\n        \"id generator returned null for \" + entity.getClass().getName()\n        + \" - check GenerationType vs actual column definition\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    em.getTransaction().begin();\n    em.persist(e);\n    em.getTransaction().commit();\n} catch (IdentifierGenerationException ex) {\n    // generated keys came back null: verify DDL (AUTO_INCREMENT/IDENTITY/SERIAL),\n    // driver version, and dialect; consider switching to SEQUENCE/UUID\n    throw new IllegalStateException(\"id generation broken for \" + e.getClass(), ex);\n}","preventionTips":["Let Hibernate/Liquibase own the schema so column definitions always match the GenerationType","Prefer SEQUENCE (with allocation) or UUID over IDENTITY for portability and batch-friendliness","Run insert+flush smoke tests per database/driver pair in CI (including testcontainers images)","Watch for insert triggers that swallow generated-key returns on Oracle/SQL Server"],"tags":["identifiers","id-generation","identity","dialect","schema-drift","hibernate"],"backgroundTag":"null-generated-identifier","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}