{"record":{"id":"9f41955b5857a7fa","repo":"hibernate/hibernate-orm","slug":"updating-immutable-entity-that-is-not-in-session-y","errorCode":null,"errorMessage":"Updating immutable entity that is not in session yet","messagePattern":"Updating immutable entity that is not in session yet","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java","lineNumber":196,"sourceCode":"\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\tvalues,\n\t\t\t\t\t\t\tincomingOldValues,\n\t\t\t\t\t\t\toldVersion,\n\t\t\t\t\t\t\tincomingDirtyAttributeIndexes,\n\t\t\t\t\t\t\tsession,\n\t\t\t\t\t\t\tversionMapping\n\t\t\t\t\t);\n\t\t\tif ( generatedValuesAccess != null ) {\n\t\t\t\treturn generatedValuesAccess.get();\n\t\t\t}\n\t\t}\n\n\t\tfinal var entry = session.getPersistenceContextInternal().getEntry( entity );\n\n\t\t// Ensure that an immutable or non-modifiable entity is not being updated unless it is\n\t\t// in the process of being deleted.\n\t\tif ( entry == null && !entityPersister().isMutable() ) {\n\t\t\tthrow new IllegalStateException( \"Updating immutable entity that is not in session yet\" );\n\t\t}\n\n\t\t// apply any pre-update in-memory value generation\n\t\tfinal int[] preUpdateGeneratedAttributeIndexes = preUpdateInMemoryValueGeneration( entity, values, session );\n\t\tfinal int[] dirtyAttributeIndexes =\n\t\t\t\tdirtyAttributeIndexes( incomingDirtyAttributeIndexes, preUpdateGeneratedAttributeIndexes );\n\n\t\tfinal boolean temporalExcludedUpdate =\n\t\t\t\tentityPersister().excludedFromTemporalVersioning( dirtyAttributeIndexes, hasDirtyCollection );\n\n\t\tfinal boolean[] attributeUpdateability;\n\t\tfinal boolean forceDynamicUpdate;\n\t\tif ( temporalExcludedUpdate ) {\n\t\t\tattributeUpdateability = getPropertiesToUpdate( dirtyAttributeIndexes, hasDirtyCollection );\n\t\t\tfor ( int i = 0; i < attributeUpdateability.length; i++ ) {\n\t\t\t\tif ( attributeUpdateability[i] && !entityPersister().isPropertyTemporalExcluded( i ) ) {\n\t\t\t\t\tattributeUpdateability[i] = false;\n\t\t\t\t}","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java#L178-L214","documentation":"UpdateCoordinatorStandard.update asserts an invariant: an immutable entity may only be updated if it is already managed in the session (PersistenceContext entry present). When entry == null and entityPersister().isMutable() is false, IllegalStateException 'Updating immutable entity that is not in session yet' is thrown - Hibernate refuses to reattach and write a detached immutable instance.","triggerScenarios":"session.update() or session.saveOrUpdate() with a detached (or transient, unsaved) instance of an @Immutable entity or one mapped with mutable=\"false\"; generic reattachment code shared between mutable and immutable types; DTO round-trips that return copies of immutable reference data and feed them to update().","commonSituations":"Reference-data entities (currencies, countries, enums-as-tables) marked @Immutable then reattached from a cache; @Immutable combined with second-level cache where detached instances leak into update paths; copy-pasted DAO code calling update() unconditionally.","solutions":["Do not update immutable entities: load the managed instance (session.get / EntityManager.find) and treat it as read-only","If the data genuinely must change, remove @Immutable / mutable=\"false\" from the mapping","Guard reattachment code: check session.contains(entity) and the persister's mutability before calling update()","Use merge() only for state that is allowed to be re-bound, and never expect writes for immutable types"],"exampleFix":"// before: reattaching a detached immutable entity\nCountry detached = fromCache(code);\nsession.update(detached); // IllegalStateException: immutable + not in session\n\n// after: immutable data is read-only; just load the managed instance\nCountry managed = session.get(Country.class, code);\n// ... or, if updates are truly required, remove @Immutable from the mapping","handlingStrategy":"validation","validationCode":"// before session.update() on any entity\nif (!session.contains(entity)) {\n    EntityPersister p = session.getEntityPersister(null, entity);\n    if (!p.isMutable()) {\n        throw new IllegalArgumentException(\n            \"Refusing to update detached immutable entity \" + p.getEntityName() + \"; load it instead\");\n    }\n}\nsession.update(entity);","typeGuard":null,"tryCatchPattern":"try { session.update(entity); } catch (IllegalStateException e) { if (e.getMessage().contains(\"immutable\")) { /* load managed instance instead of reattaching; immutable data is read-only */ } throw e; }","preventionTips":["Treat @Immutable entities as read-only reference data; never reattach them with update()","Keep immutable instances inside the session or cache managed instances instead of detached copies","Add a shared guard (contains + isMutable) before generic reattachment code"],"tags":["hibernate","orm","immutable-entities","session","detached","update"],"backgroundTag":"immutable-entity-update","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}