{"record":{"id":"00b5743a23bdf857","repo":"hibernate/hibernate-orm","slug":"lock-mode-not-supported-for-read-only-entity","errorCode":null,"errorMessage":"Lock mode {} not supported for read-only entity","messagePattern":"Lock mode (.+?) not supported for read-only entity","errorType":"exception","errorClass":"UnsupportedLockAttemptException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryImpl.java","lineNumber":171,"sourceCode":"\t\tsetDeletedState( deletedState );\n\t\tthis.version = version;\n\t\tsetCompressedValue( LOCK_MODE, lockMode );\n\t\tsetCompressedValue( EXISTS_IN_DATABASE, existsInDatabase );\n\t\tthis.rowId = null; // this is equivalent to the old behavior...\n\t\t// don't store PersistenceContext for immutable entity, see HHH-10251\n\t\tthis.persistenceContext = mutable ? persistenceContext : null;\n\t}\n\n\t@Override\n\tpublic LockMode getLockMode() {\n\t\treturn getCompressedValue( LOCK_MODE );\n\t}\n\n\t@Override\n\tpublic void setLockMode(LockMode lockMode) {\n\t\tif ( lockMode.greaterThan( LockMode.READ )\n\t\t\t\t&& persister!=null && !persister.isMutable() ) {\n\t\t\tthrow new UnsupportedLockAttemptException( \"Lock mode \" + lockMode\n\t\t\t\t\t\t\t+ \" not supported for read-only entity\" );\n\t\t}\n\t\tsetCompressedValue( LOCK_MODE, lockMode );\n\t}\n\n\t@Override\n\tpublic Status getStatus() {\n\t\treturn getCompressedValue( STATUS );\n\t}\n\n\tprivate Status getPreviousStatus() {\n\t\treturn getCompressedValue( PREVIOUS_STATUS );\n\t}\n\n\t@Override\n\tpublic void setStatus(Status status) {\n\t\tif ( status == READ_ONLY ) {\n\t\t\t//memory optimization","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryImpl.java#L153-L189","documentation":"EntityEntryImpl.setLockMode refuses lock modes that are greater than LockMode.READ (OPTIMISTIC, OPTIMISTIC_FORCE_INCREMENT, WRITE, PESSIMISTIC_*) when the entity's persister is immutable (@Immutable entity, or otherwise mapped non-mutable). Immutable entities are never versioned or lock-upgraded, so requesting a real lock on them raises UnsupportedLockAttemptException instead of silently ignoring the lock.","triggerScenarios":"Calling em.lock / session.lock, em.find(id, LockModeType.PESSIMISTIC_WRITE), refresh(entity, lockMode), setting jakarta.lock.scope/lock-mode hints, or a @Lock annotation on a repository method for an entity mapped @Immutable (or a read-only-mapped persister where isMutable() is false) with any lock mode above READ.","commonSituations":"Adding pessimistic locking to a query that touches an audit/lookup table mapped @Immutable; @Version fields removed while lock annotations remain; generic locking aspects applied to all repositories including immutable ones; Spring Data @Lock on repositories of immutable projections.","solutions":["Remove the lock request (annotation, find/lock argument, or query hint) for the immutable entity - LockMode.READ/NONE are the only accepted modes.","If the data really needs locking, map the entity mutable (drop @Immutable / set mutable) - accepting the update semantics that brings.","Catch org.hibernate.UnsupportedLockAttemptException to degrade gracefully when generic locking code hits immutable entities.","Audit generic lock aspects/interceptors so they skip entities whose persister reports isMutable() == false."],"exampleFix":"// before - @Immutable entity with pessimistic lock\n@Immutable @Entity\npublic class ExchangeRate { ... }\n\nem.find(ExchangeRate.class, id, LockModeType.PESSIMISTIC_WRITE); // throws\n// after - no lock upgrade on immutable data\nem.find(ExchangeRate.class, id);\n// (or map the entity mutable if locking is genuinely required)","handlingStrategy":"validation","validationCode":"// Guard: skip lock requests on immutable entities\nboolean mutable = session.getFactory().getRuntimeMetamodels()\n        .getMappingMetamodel().getEntityDescriptor(entityClass).isMutable();\nif (!mutable && lockMode.greaterThan(org.hibernate.LockMode.READ)) {\n    log.debug(\"skipping lock {} for immutable entity {}\", lockMode, entityClass);\n} else {\n    session.buildLockRequest(new LockOptions(lockMode)).lock(entity);\n}","typeGuard":"static boolean supportsLockUpgrade(org.hibernate.Session session, Class<?> entityClass) {\n    return session.getFactory().getRuntimeMetamodels()\n            .getMappingMetamodel().getEntityDescriptor(entityClass).isMutable();\n}","tryCatchPattern":"try {\n    session.buildLockRequest(new LockOptions(lockMode)).lock(entity);\n} catch (org.hibernate.UnsupportedLockAttemptException e) {\n    // entity is @Immutable and lockMode > READ: degrade to no lock\n    log.debug(\"lock not applicable to immutable entity\", e);\n}","preventionTips":["Do not annotate entities @Lock or pass LockModeType to find() for @Immutable entities.","Exclude immutable entities from generic locking aspects/interceptors.","Catch UnsupportedLockAttemptException in shared data-access templates so locking is best-effort."],"tags":["locking","immutable-entity","pessimistic-lock","optimistic-lock","mapping"],"backgroundTag":"lock-on-immutable-entity","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}