hibernate/hibernate-orm · error · IllegalStateException
Session is in read-only mode
Error message
Session is in read-only mode
What it means
The session was opened in read-only mode (SessionBuilder.readOnly(true) / SessionCreationOptions.isReadOnly()), and checkNotReadOnly() guards every state-changing operation (persist, merge, remove, update, delete, mutating queries, flush). Any write attempt fails with IllegalStateException before the database is touched. Read-only mode is a contract, not a hint: Hibernate blocks writes rather than silently dropping or allowing them.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java:865
);
}
@Nullable
private static Object getTenantId( SessionFactoryOptions factoryOptions, SessionCreationOptions options ) {
final Object tenantIdentifier = options.getTenantIdentifierValue();
if ( factoryOptions.isMultiTenancyEnabled() && tenantIdentifier == null ) {
throw new HibernateException( "SessionFactory configured for multi-tenancy, but no tenant identifier specified" );
}
return tenantIdentifier;
}
boolean isReadOnly() {
return readOnly;
}
void checkNotReadOnly() {
if ( isReadOnly() ) {
throw new IllegalStateException( "Session is in read-only mode" );
}
}
@Nonnull
private static SessionEventListenerManager createSessionEventsManager(
SessionFactoryOptions factoryOptions, SessionCreationOptions options) {
final var customListeners = options.getCustomSessionEventListeners();
return customListeners == null
? new SessionEventListenerManagerImpl( factoryOptions.buildSessionEventListeners() )
: new SessionEventListenerManagerImpl( customListeners );
}
/**
* Override the implementation provided on SharedSessionContractImplementor
* which is not very efficient: this method is hot in Hibernate Reactive, and could
* be hot in some ORM contexts as well.
*/
@OverrideView on GitHub (pinned to fad1729dce)
Solutions
- Run writes in their own non-read-only transactional method (@Transactional(readOnly=false)); keep readOnly=true strictly for query methods.
- If the session came from withOptions().readOnly(true), open a normal session for the mutating work.
- Split large service methods so read and write concerns have separate transaction boundaries and annotations.
Example fix
// before
@Transactional(readOnly = true)
public CustomerDto load(Long id) {
repo.saveCounter(id); // IllegalStateException: Session is in read-only mode
}
// after
@Transactional(readOnly = true)
public CustomerDto load(Long id) { return readRepo.find(id); }
@Transactional
public void recordVisit(Long id) { repo.saveCounter(id); } Defensive patterns
Strategy: validation
Validate before calling
if (session.isDefaultReadOnly()) {
throw new UnsupportedOperationException(
"Session opened read-only; mutations require a writable transaction");
}
session.persist(entity); Prevention
- Keep @Transactional(readOnly = true) strictly on verified query methods
- Review service methods for 'one small write' inside read-only transactions after upgrading Hibernate
- In test fixtures that use readOnly sessions, do cleanup with a separate writable session
When it happens
Trigger: sessionFactory.withOptions().readOnly(true).openSession() (or Spring @Transactional(readOnly=true) propagating Hibernate session read-only) followed by session.persist/merge/remove, createMutationQuery(...).executeUpdate(), or session.flush().
Common situations: A @Transactional(readOnly=true) service method that 'just this once' performs a write; upgrading to Hibernate 6.4+/7 where read-only session enforcement became explicit and previously tolerated writes now throw; test fixtures opening read-only sessions for speed and then mutating data in cleanup.
Related errors
- Can't update read-only object
- Can't write to a read-only object
- Cannot lazily initialize collection
- Illegal attempt to associate a collection with two open sess
- Cannot redefine the read-only mode on a child session if the
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/868bb7770f6119f1.
Report an issue: GitHub.