What Is Redis SETNX?

SETNX is a Redis string command whose name stands for "SET if Not eXists." It sets a key to a given value only if that key does not already exist. If the key is already present, SETNX does nothing and leaves the existing value untouched. The command works identically in Valkey, which inherits the same command set from Redis.

This "set only if absent" behavior makes SETNX a natural building block for a common distributed-systems problem: letting exactly one client claim a resource when many clients are competing for it. For that reason, SETNX is most often encountered as the classic, low-level way to implement a distributed lock. As we'll see, it is also a command that modern Redis has deprecated, and building a correct lock with it is harder than it first appears.

How the SETNX Command Works

The command takes a key and a value:

SETNX key value

SETNX returns an integer reply that tells you whether the write happened:

  • 1 — the key did not exist and was set to value.
  • 0 — the key already existed and was left unchanged.

For example:

SETNX job:lock "worker-1"   # returns 1 — the key was created
SETNX job:lock "worker-2"   # returns 0 — the key already exists, nothing changes
GET job:lock                # "worker-1"

Because the check-and-set happens as a single atomic operation on the Redis server, two clients issuing SETNX on the same key can never both receive 1. Exactly one wins. That atomicity is the whole reason the command is useful for coordination.

SETNX Is Deprecated: Use SET With the NX Option

As of Redis 2.6.12, SETNX is considered deprecated. New code should use the extended SET command with the NX flag instead, which does the same thing and can also set an expiration in the same atomic step:

SET key value NX          # set only if key is absent (returns OK, or nil if not set)
SET key value NX PX 30000 # same, but also attach a 30-second TTL atomically

The SET ... NX form differs from SETNX in its reply: it returns OK when the write succeeds and nil when the key already exists, rather than 1 or 0. The important practical advantage is the ability to combine "set if not exists" with an expiration (EX seconds or PX milliseconds) in a single command. That single detail is what separates a safe lock from a broken one, as the next section explains.

There is also a multi-key variant, MSETNX, which sets several key-value pairs only if none of the keys already exist. Unlike SETNX, it is not deprecated — there is no single SET-based command that reproduces its all-or-nothing behavior across multiple keys — though it sees relatively little use in practice.

SETNX and Distributed Locks

The canonical use case for SETNX is a lock. A key represents the resource, and a client "acquires the lock" by successfully creating that key. Because only one client can create a key that doesn't yet exist, only one client holds the lock at a time — which is exactly what you need to prevent a race condition when many application instances contend for the same resource.

A naive implementation looks like this:

SETNX lock:order:123 <token>   # try to acquire
EXPIRE lock:order:123 30       # then give the lock a 30-second timeout

This looks reasonable, but it hides two well-known correctness bugs.

The acquisition is not atomic. SETNX and EXPIRE are two separate commands. If the client crashes, or the connection drops, in the gap between them, the lock key is created but never gets a TTL. No timeout means the key lives forever, and the resource stays locked until someone manually intervenes — a deadlock. The fix is to acquire the lock and set its expiration in one atomic command:

SET lock:order:123 <token> NX PX 30000

Releasing the lock is unsafe. A client that simply runs DEL lock:order:123 to release the lock can accidentally delete a lock it no longer owns. If the original holder was slow and its TTL already expired, another client may have since acquired the lock — and the slow client would now delete that second client's lock. The safe pattern is to store a unique token as the value and delete the key only if the token still matches, using a Lua script so the check and delete run atomically:

if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end

Even with both fixes, a raw SET ... NX lock still lacks features that production systems usually need: automatic renewal for work that legitimately runs longer than the TTL, reentrancy so the same thread can acquire a lock it already holds, fair ordering of waiters, and safe coordination across a Redis cluster. Implementing all of this correctly by hand is error-prone, which is why most teams reach for a client library that provides it.

Distributed Locks on Java With Redisson

Redisson is a Redis Java client that provides distributed locking as a first-class Java object, so you never have to assemble SET ... NX PX calls, unique tokens, and Lua release scripts yourself. Redisson's RLock implements the standard java.util.concurrent.locks.Lock interface, so it behaves like a lock you already know — the difference is that its state lives in Redis and is therefore shared across every JVM in your deployment.

RLock lock = redisson.getLock("lock:order:123");

lock.lock();
try {
    // critical section — coordinated across every instance in the cluster
} finally {
    lock.unlock();
}

RLock handles the correctness concerns that make hand-rolled SETNX locks fragile:

  • Atomic acquisition with a TTL, so a crashed client can never hold a lock forever.
  • A lock watchdog that automatically extends the lease while the owner is still alive (30 seconds by default, configurable via lockWatchdogTimeout), so long-running work isn't cut off by an expiring key.
  • Owner-safe release — only the thread that acquired the lock can release it, or an IllegalMonitorStateException is thrown.
  • Reentrancy, so the same thread can acquire the lock multiple times without deadlocking itself.

You can also bound how long you wait for the lock and how long you hold it, which removes the need to manage TTLs manually:

// wait up to 100 seconds to acquire, then hold for at most 10 seconds
boolean acquired = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (acquired) {
    try {
        // critical section
    } finally {
        lock.unlock();
    }
}

Beyond the basic reentrant lock, Redisson offers fair locks, read/write locks, multi-locks that span independent Redis instances, and the Redlock-style guarantees that raw SETNX can't provide on its own. For a hands-on walkthrough, see the guide on how to use Redis locks in Java with Redisson.

In short: SETNX is a useful primitive to understand, but a deprecated one, and a correct lock needs more than it offers. To coordinate access to shared resources across a distributed Java application, try Redisson for free today.

Similar Terms