hibernate/hibernate-orm · error · IllegalStateException
Updating immutable entity that is not in session yet
Error message
Updating immutable entity that is not in session yet
What it means
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.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java:196
id,
values,
incomingOldValues,
oldVersion,
incomingDirtyAttributeIndexes,
session,
versionMapping
);
if ( generatedValuesAccess != null ) {
return generatedValuesAccess.get();
}
}
final var entry = session.getPersistenceContextInternal().getEntry( entity );
// Ensure that an immutable or non-modifiable entity is not being updated unless it is
// in the process of being deleted.
if ( entry == null && !entityPersister().isMutable() ) {
throw new IllegalStateException( "Updating immutable entity that is not in session yet" );
}
// apply any pre-update in-memory value generation
final int[] preUpdateGeneratedAttributeIndexes = preUpdateInMemoryValueGeneration( entity, values, session );
final int[] dirtyAttributeIndexes =
dirtyAttributeIndexes( incomingDirtyAttributeIndexes, preUpdateGeneratedAttributeIndexes );
final boolean temporalExcludedUpdate =
entityPersister().excludedFromTemporalVersioning( dirtyAttributeIndexes, hasDirtyCollection );
final boolean[] attributeUpdateability;
final boolean forceDynamicUpdate;
if ( temporalExcludedUpdate ) {
attributeUpdateability = getPropertiesToUpdate( dirtyAttributeIndexes, hasDirtyCollection );
for ( int i = 0; i < attributeUpdateability.length; i++ ) {
if ( attributeUpdateability[i] && !entityPersister().isPropertyTemporalExcluded( i ) ) {
attributeUpdateability[i] = false;
}View on GitHub (pinned to fad1729dce)
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
Example fix
// before: reattaching a detached immutable entity Country detached = fromCache(code); session.update(detached); // IllegalStateException: immutable + not in session // after: immutable data is read-only; just load the managed instance Country managed = session.get(Country.class, code); // ... or, if updates are truly required, remove @Immutable from the mapping
Defensive patterns
Strategy: validation
Validate before calling
// before session.update() on any entity
if (!session.contains(entity)) {
EntityPersister p = session.getEntityPersister(null, entity);
if (!p.isMutable()) {
throw new IllegalArgumentException(
"Refusing to update detached immutable entity " + p.getEntityName() + "; load it instead");
}
}
session.update(entity); Try / catch
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; } Prevention
- 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
When it happens
Trigger: 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().
Common situations: 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.
Related errors
- Cannot lazily initialize collection
- Illegal attempt to associate a collection with two open sess
- Could not reassociate uninitialized transient collection
- SessionFactory configured for multi-tenancy, but no tenant i
- Unable to resolve TableMapping for selectable - %s
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/9f41955b5857a7fa.
Report an issue: GitHub.