hibernate/hibernate-orm · error · UnsupportedOperationException
immutable global instance of LockOptions
Error message
immutable global instance of LockOptions
What it means
Hibernate shares a set of pre-built global LockOptions constants (LockOptions.NONE, READ, UPGRADE, and package-private OPTIMISTIC/PESSIMISTIC_* variants, defined around LockOptions.java:650-727). These singletons are flagged immutable, and any mutating setter such as setLockMode (LockOptions.java:207) throws UnsupportedOperationException to stop one caller from corrupting locking behavior for every other session in the JVM. The exception therefore means you are mutating a shared static instance rather than your own LockOptions.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/LockOptions.java:207
/**
* Retrieve the overall lock mode in effect for this set of options.
*
* @return the overall lock mode
*/
public LockMode getLockMode() {
return lockMode;
}
/**
* Set the overall {@linkplain LockMode lock mode}. The default is
* {@link LockMode#NONE}, that is, no locking at all.
*
* @param lockMode the new overall lock mode
* @return {@code this} for method chaining
*/
public LockOptions setLockMode(LockMode lockMode) {
if ( immutable ) {
throw new UnsupportedOperationException("immutable global instance of LockOptions");
}
if ( lockMode == LockMode.UPGRADE_NOWAIT ) {
timeout = Timeouts.NO_WAIT_MILLI;
}
else if ( lockMode == LockMode.UPGRADE_SKIPLOCKED ) {
timeout = Timeouts.SKIP_LOCKED_MILLI;
}
this.lockMode = lockMode;
return this;
}
/**
* The timeout associated with {@code this} options, defining a maximum
* amount of time that the database should wait to obtain a pessimistic
* lock before returning an error to the client.
*/
public Timeout getTimeout() {
return Timeout.milliseconds( getTimeOut() );View on GitHub (pinned to fad1729dce)
Solutions
- Always build your own instance: new LockOptions(LockMode.PESSIMISTIC_WRITE) or new LockOptions().setLockMode(mode)
- In methods that accept an optional LockOptions, treat the argument as read-only and copy before mutating: options = (options == null || options == LockOptions.NONE) ? new LockOptions() : options
- Never store or mutate the public static constants; only read them
- Search for `.setLockMode(` call sites whose receiver may originate from a static constant or an external API
Example fix
// before - mutates the shared global constant LockOptions.NONE.setLockMode(LockMode.PESSIMISTIC_WRITE); session.buildLockRequest(LockOptions.NONE).lock(entity); // after - use a private mutable instance LockOptions options = new LockOptions(LockMode.PESSIMISTIC_WRITE); session.buildLockRequest(options).lock(entity);
Defensive patterns
Strategy: validation
Validate before calling
// never mutate a LockOptions you did not construct
static LockOptions mutable(LockOptions candidate) {
if (candidate == null || candidate == LockOptions.NONE
|| candidate == LockOptions.READ || candidate == LockOptions.UPGRADE) {
return new LockOptions();
}
return candidate;
} Type guard
static boolean isMutableLockOptions(LockOptions options) {
return options != null
&& options != LockOptions.NONE
&& options != LockOptions.READ
&& options != LockOptions.UPGRADE;
} Prevention
- Treat every LockOptions you receive from an API as read-only; copy into a new LockOptions before customizing
- Never use LockOptions.NONE and friends as mutable defaults in method signatures
- Static-analysis rule of thumb: any '.set' call whose receiver is a public static constant is a bug
When it happens
Trigger: Calling setLockMode(...) on LockOptions.NONE, LockOptions.READ, or LockOptions.UPGRADE; or on a LockOptions instance that a helper method received as a default parameter (commonly defaulting to LockOptions.NONE) and then customizes in place.
Common situations: Utility methods written as `void query(..., LockOptions options = LockOptions.NONE)` that then call options.setLockMode(...); code that worked on Hibernate 5.x where these constants were mutable shared instances; upgrading to Hibernate 6.2+/7 where the immutability guard was added; caching a returned LockOptions and tuning it later.
Related errors
- WRITE is not a valid LockMode as an argument
- Spanner does not support no wait.
- Spanner does not support skip locked.
- Spanner does not support lock timeout.
- Spanner doesn't support for-update with no-wait timeout
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/647f9c64ed09e12b.
Report an issue: GitHub.