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

  1. Do not update immutable entities: load the managed instance (session.get / EntityManager.find) and treat it as read-only
  2. If the data genuinely must change, remove @Immutable / mutable="false" from the mapping
  3. Guard reattachment code: check session.contains(entity) and the persister's mutability before calling update()
  4. 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

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


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/9f41955b5857a7fa. Report an issue: GitHub.