{"record":{"id":"e7ca0d4eb0615bea","repo":"hibernate/hibernate-orm","slug":"immutable-collection-dereferenced-by-owner","errorCode":null,"errorMessage":"Immutable collection dereferenced by owner: {}","messagePattern":"Immutable collection dereferenced by owner: (.+?)","errorType":"exception","errorClass":"HibernateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java","lineNumber":302,"sourceCode":"\t\t\telse if ( collection.isDirty() ) {\n\t\t\t\t// the collection's elements have changed\n\t\t\t\tflushProcessingContext.queueCollectionUpdate(\n\t\t\t\t\t\tcollection,\n\t\t\t\t\t\tloadedPersister,\n\t\t\t\t\t\tcollectionEntry.getLoadedKey(),\n\t\t\t\t\t\tcollectionEntry.isSnapshotEmpty( collection )\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate static void checkOnChangedOwner(PersistentCollection<?> collection, CollectionEntry collectionEntry, CollectionPersister loadedPersister, CollectionPersister currentPersister) {\n\t\tfinal boolean immutableDereferenced =\n\t\t\t\tcollectionEntry.isReadOnly()\n\t\t\t\t&& loadedPersister != null\n\t\t\t\t&& !isOwnerDeletedOrGone( collection );\n\t\tif ( immutableDereferenced ) {\n\t\t\tthrow new HibernateException( \"Immutable collection dereferenced by owner: \"\n\t\t\t\t\t\t+ collectionInfoString( loadedPersister.getRole(), collectionEntry.getLoadedKey() ) );\n\t\t}\n\n\n\t\tfinal boolean orphanDeleteAndRoleChanged =\n\t\t\t\tloadedPersister != null\n\t\t\t\t&& currentPersister != null\n\t\t\t\t&& loadedPersister.hasOrphanDelete();\n\t\tif ( orphanDeleteAndRoleChanged ) {\n\t\t\tthrow new HibernateException(\n\t\t\t\t\t\"Collection with orphan orphan delete enabled has modifier owner: \"\n\t\t\t\t\t+ collectionInfoString( loadedPersister.getRole(), collectionEntry.getLoadedKey() ) );\n\t\t}\n\t}\n\n\tprivate static boolean isOwnerDeletedOrGone(PersistentCollection<?> collection) {\n\t\tfinal Object owner = collection.getOwner();\n\t\tassert owner != null;","sourceCodeStart":284,"sourceCodeEnd":320,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java#L284-L320","documentation":"checkOnChangedOwner validates collections whose entry is read-only (loaded as immutable - read-only session/entity or immutable mapping). If such a collection is dereferenced (the owner's property now points at a different collection or null) while the owner is still alive (not deleted/gone), Hibernate throws: an immutable collection cannot be replaced, because the loaded state is the database truth and no snapshot/update path exists for it.","triggerScenarios":"Calling a setter that replaces a collection on an entity loaded read-only (session.setDefaultReadOnly(true), sharedSessionBuilder .readOnly(true), @Immutable-adjacent read-only entries), or a collection mapped immutable, then dereferencing it (set X(null) or setX(newCollection)) and flushing while the owner is managed.","commonSituations":"Read-only report sessions upgraded to write sessions; entities loaded with LockOptions.READ_ONLY then modified; mapping collections to immutable where the UI still lets users replace lists; load-then-modify flows on top of 'optimization' read-only flags.","solutions":["Load the entity in read-write mode before modifying it: session.setDefaultReadOnly(false) / avoid readOnly() on the load.","Make the collection mapping mutable (remove immutable=true / ensure the owner is not loaded read-only).","Do not replace the collection on read-only entities - delete and reinsert the owner if the data must change.","Check collectionInfoString in the message to identify which role/owner triggered it and fix that code path."],"exampleFix":"// before - entity loaded read-only, collection replaced\nSession ro = sf.withOptions().readOnly(true).openSession();\nOrder o = ro.find(Order.class, id);\no.setLines(new ArrayList<>());      // dereference -> HibernateException at flush\n// after - load read-write for modification\nSession rw = sf.openSession();\nOrder o = rw.find(Order.class, id);\no.getLines().clear();","handlingStrategy":"validation","validationCode":"// Guard: verify the entity is writable before letting code replace its collections\nboolean writable = session.getEntityPersister(null, entity).isMutable()\n        && !session.isReadOnly(entity);\nif (!writable) throw new IllegalStateException(\n    \"Entity loaded read-only; collection replacement not allowed - reload read-write first\");","typeGuard":null,"tryCatchPattern":"catch (HibernateException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Immutable collection dereferenced by owner\")) {\n        // reload the owner in a read-write session before editing\n        throw new MappingMisuseException(e.getMessage(), e);\n    }\n    throw e;\n}","preventionTips":["Keep read-only loads strictly read: separate report sessions from edit sessions.","Check session.isReadOnly(entity) in update flows before mutating collections.","Do not map collections as immutable if the application ever needs to replace them."],"tags":["collection","read-only","immutable","flush","entity-state"],"backgroundTag":"immutable-collection-modification","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}