{"record":{"id":"868bb7770f6119f1","repo":"hibernate/hibernate-orm","slug":"session-is-in-read-only-mode","errorCode":null,"errorMessage":"Session is in read-only mode","messagePattern":"Session is in read-only mode","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java","lineNumber":865,"sourceCode":"\t\t);\n\t}\n\n\t@Nullable\n\tprivate static Object getTenantId( SessionFactoryOptions factoryOptions, SessionCreationOptions options ) {\n\t\tfinal Object tenantIdentifier = options.getTenantIdentifierValue();\n\t\tif ( factoryOptions.isMultiTenancyEnabled() && tenantIdentifier == null ) {\n\t\t\tthrow new HibernateException( \"SessionFactory configured for multi-tenancy, but no tenant identifier specified\" );\n\t\t}\n\t\treturn tenantIdentifier;\n\t}\n\n\tboolean isReadOnly() {\n\t\treturn readOnly;\n\t}\n\n\tvoid checkNotReadOnly() {\n\t\tif ( isReadOnly() ) {\n\t\t\tthrow new IllegalStateException( \"Session is in read-only mode\" );\n\t\t}\n\t}\n\n\t@Nonnull\n\tprivate static SessionEventListenerManager createSessionEventsManager(\n\t\t\tSessionFactoryOptions factoryOptions, SessionCreationOptions options) {\n\t\tfinal var customListeners = options.getCustomSessionEventListeners();\n\t\treturn customListeners == null\n\t\t\t\t? new SessionEventListenerManagerImpl( factoryOptions.buildSessionEventListeners() )\n\t\t\t\t: new SessionEventListenerManagerImpl( customListeners );\n\t}\n\n\t/**\n\t * Override the implementation provided on SharedSessionContractImplementor\n\t * which is not very efficient: this method is hot in Hibernate Reactive, and could\n\t * be hot in some ORM contexts as well.\n\t */\n\t@Override","sourceCodeStart":847,"sourceCodeEnd":883,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java#L847-L883","documentation":"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.","triggerScenarios":"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().","commonSituations":"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.","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."],"exampleFix":"// before\n@Transactional(readOnly = true)\npublic CustomerDto load(Long id) {\n    repo.saveCounter(id); // IllegalStateException: Session is in read-only mode\n}\n// after\n@Transactional(readOnly = true)\npublic CustomerDto load(Long id) { return readRepo.find(id); }\n\n@Transactional\npublic void recordVisit(Long id) { repo.saveCounter(id); }","handlingStrategy":"validation","validationCode":"if (session.isDefaultReadOnly()) {\n    throw new UnsupportedOperationException(\n        \"Session opened read-only; mutations require a writable transaction\");\n}\nsession.persist(entity);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["hibernate","session","read-only","spring","transactions"],"backgroundTag":"read-only-session-write","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}