{"record":{"id":"647f9c64ed09e12b","repo":"hibernate/hibernate-orm","slug":"immutable-global-instance-of-lockoptions","errorCode":null,"errorMessage":"immutable global instance of LockOptions","messagePattern":"immutable global instance of LockOptions","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/LockOptions.java","lineNumber":207,"sourceCode":"\t/**\n\t * Retrieve the overall lock mode in effect for this set of options.\n\t *\n\t * @return the overall lock mode\n\t */\n\tpublic LockMode getLockMode() {\n\t\treturn lockMode;\n\t}\n\n\t/**\n\t * Set the overall {@linkplain LockMode lock mode}. The default is\n\t * {@link LockMode#NONE}, that is, no locking at all.\n\t *\n\t * @param lockMode the new overall lock mode\n\t * @return {@code this} for method chaining\n\t */\n\tpublic LockOptions setLockMode(LockMode lockMode) {\n\t\tif ( immutable ) {\n\t\t\tthrow new UnsupportedOperationException(\"immutable global instance of LockOptions\");\n\t\t}\n\t\tif ( lockMode == LockMode.UPGRADE_NOWAIT ) {\n\t\t\ttimeout = Timeouts.NO_WAIT_MILLI;\n\t\t}\n\t\telse if ( lockMode == LockMode.UPGRADE_SKIPLOCKED ) {\n\t\t\ttimeout = Timeouts.SKIP_LOCKED_MILLI;\n\t\t}\n\t\tthis.lockMode = lockMode;\n\t\treturn this;\n\t}\n\n\t/**\n\t * The timeout associated with {@code this} options, defining a maximum\n\t * amount of time that the database should wait to obtain a pessimistic\n\t * lock before returning an error to the client.\n\t */\n\tpublic Timeout getTimeout() {\n\t\treturn Timeout.milliseconds( getTimeOut() );","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/LockOptions.java#L189-L225","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before - mutates the shared global constant\nLockOptions.NONE.setLockMode(LockMode.PESSIMISTIC_WRITE);\nsession.buildLockRequest(LockOptions.NONE).lock(entity);\n\n// after - use a private mutable instance\nLockOptions options = new LockOptions(LockMode.PESSIMISTIC_WRITE);\nsession.buildLockRequest(options).lock(entity);","handlingStrategy":"validation","validationCode":"// never mutate a LockOptions you did not construct\nstatic LockOptions mutable(LockOptions candidate) {\n    if (candidate == null || candidate == LockOptions.NONE\n            || candidate == LockOptions.READ || candidate == LockOptions.UPGRADE) {\n        return new LockOptions();\n    }\n    return candidate;\n}","typeGuard":"static boolean isMutableLockOptions(LockOptions options) {\n    return options != null\n        && options != LockOptions.NONE\n        && options != LockOptions.READ\n        && options != LockOptions.UPGRADE;\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["locking","immutability","shared-constant","api-misuse","hibernate"],"backgroundTag":"immutable-shared-instance-mutation","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}