alibaba/nacos · error · IllegalMonitorStateException

Unlock rejected by server, key=

Error message

Unlock rejected by server, key={}, msg={}

What it means

Thrown by NacosLock.unlock() when the server's RELEASE operation returns a non-success LockResult. The client sends an unlock request via grpcClient.unLockWithResult(instance); if result.isSuccess() is false, it clears local state (resets reentrant count, unregisters the watchdog) and then throws IllegalMonitorStateException carrying the server's error message. The local state is force-cleared so the lock does not become permanently unusable, but the server-side error (e.g. ownership mismatch, already released, expired) is surfaced.

Solutions

  1. Inspect the server error message embedded in the exception (result.getErrorMessage()) to determine whether it was an ownership mismatch, already-released, or expiry.
  2. Ensure the watchdog renewal mechanism is healthy (check logs for renewal failures) before relying on the lock to still be held.
  3. Set an appropriate expiredTime/lease and avoid holding the lock far beyond it; prefer the JUC lock()/lockInterruptibly() path which auto-renews.
  4. Treat this exception as non-fatal for local state — local client state is already cleared, so a retry with a fresh lock() cycle is safe.

Example fix

// before
lock.unlock(); // propagates IllegalMonitorStateException on server reject

// after
try {
    lock.unlock();
} catch (IllegalMonitorStateException e) {
    LOGGER.warn("Server rejected unlock for key={}, local state already cleared: {}", key, e.getMessage());
    // local reentrant count and watchdog already reset by NacosLock
}
Defensive patterns

Strategy: try-catch

Try / catch

try { lock.unlock(); } catch (IllegalMonitorStateException e) { LOGGER.warn("Server rejected unlock ({}); local state already cleared", e.getMessage()); }

Prevention

When it happens

Trigger: The lock lease already expired on the server (watchdog renewal failed silently) before unlock() is called; another client/owner holds or already released the same key; server-side lock metadata was evicted or corrupted; using a stale LockInstance whose owner token no longer matches the current server state.

Common situations: Network partitions that silently kill watchdog renewals, causing the server to expire the lease while the client still believes it holds the lock; clock skew between client and server pushing expiredTime into the past; calling unlock long after the operation completed; multiple NacosLockService instances or clients targeting the same key with different owner tokens.

Related errors


AI-assisted analysis of alibaba/nacos@9b989acdf1 (2026-08-14). Data as JSON: /api/errors/3ff9a03f6c3ab9b7. Report an issue: GitHub.

Appendix: source

Thrown at client/src/main/java/com/alibaba/nacos/client/lock/NacosLock.java:314

            if (count <= 0) {
                throw new IllegalMonitorStateException("Current thread does not hold the lock");
            }
            try {
                LockInstance instance = buildInstance(0);
                LockResult result = grpcClient.unLockWithResult(instance);
                if (result.isSuccess()) {
                    localReentrantCount.set(count - 1);
                    if (result.getReentrantCount() == 0) {
                        watchdog.unregister(key);
                        localReentrantCount.remove();
                        removed = true;
                    }
                } else {
                    localReentrantCount.set(0);
                    watchdog.unregister(key);
                    localReentrantCount.remove();
                    removed = true;
                    throw new IllegalMonitorStateException(
                        "Unlock rejected by server, key=" + key + ", msg="
                            + result.getErrorMessage());
                }
            } catch (NacosException e) {
                // Server may have already released the lock — clear client state
                // to prevent the lock from becoming permanently unusable.
                localReentrantCount.set(0);
                watchdog.unregister(key);
                localReentrantCount.remove();
                removed = true;
                LOGGER.error("Failed to unlock, key={}", key, e);
                throw new IllegalStateException("Failed to unlock: " + key, e);
            }
        } finally {
            inUnlock.remove();
            if (!removed && localReentrantCount.get() <= 0) {
                localReentrantCount.remove();
            }

View on GitHub (pinned to 9b989acdf1)