redis/redis-py · error · Exception

nx and xx are mutually exclusive: use one, the other or neit

Error message

nx and xx are mutually exclusive: use one, the other or neither - but not both

What it means

Raised by JSON.set() when both nx=True and xx=True are passed. NX means 'only set if the key/path does not exist'; XX means 'only set if it exists' — they are logically contradictory. Note: unlike most redis-py validation errors, this raises a bare Exception (not DataError), so catching redis.exceptions.DataError will NOT catch it.

Source

Thrown at redis/commands/json/commands.py:519

        with utf-8.
        ``fpha`` if set, forces Redis to use the specified floating-point type
        for storing all FP homogeneous arrays in ``obj``.
        Accepts a :class:`FPHAType` enum value or a string
        (``"BF16"``, ``"FP16"``, ``"FP32"``, ``"FP64"``).

        For the purpose of using this within a pipeline, this command is also
        aliased to JSON.SET.

        For more information see `JSON.SET <https://redis.io/commands/json.set>`_.
        """
        if decode_keys:
            obj = decode_dict_keys(obj)

        pieces = [name, str(path), self._encode(obj)]

        # Handle existential modifiers
        if nx and xx:
            raise Exception(
                "nx and xx are mutually exclusive: use one, the "
                "other or neither - but not both"
            )
        elif nx:
            pieces.append("NX")
        elif xx:
            pieces.append("XX")

        if fpha is not None:
            pieces.extend(["FPHA", FPHAType.from_value(fpha).value])

        return self.execute_command("JSON.SET", *pieces)

    @overload
    def mset(
        self: SyncClientProtocol, triplets: list[tuple[str, str, JsonType]]
    ) -> bool: ...

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Pass at most one of nx or xx (or neither for unconditional set).
  2. Derive both from a single tri-state variable: nx = (mode == 'create'), xx = (mode == 'update').
  3. Catch the bare Exception around JSON.set if the flags come from untrusted input.

Example fix

// before
client.json().set("k", "$", obj, nx=True, xx=True)
// after
client.json().set("k", "$", obj, nx=True)  # only-if-absent
Defensive patterns

Strategy: validation

Validate before calling

def json_set(client, name, path, obj, mode=None):
    if mode not in (None, "create", "update"):
        raise ValueError(f"mode must be None/create/update, got {mode}")
    return client.json().set(
        name, path, obj,
        nx=(mode == "create"),
        xx=(mode == "update"),
    )

Type guard

def valid_nx_xx(nx, xx) -> bool:
    return not (nx and xx)

Try / catch

# Note: this raises bare Exception, NOT DataError.
try:
    client.json().set("k", "$", obj, nx=nx, xx=xx)
except Exception as e:
    if "mutually exclusive" in str(e):
        # pick one based on intent
        client.json().set("k", "$", obj, nx=True)
    else:
        raise

Prevention

When it happens

Trigger: Call client.json().set(name, path, obj, nx=True, xx=True).

Common situations: Building the nx/xx flags from a single mode variable and accidentally setting both (e.g. flags parsed from a request where both fields were present); copy-paste leaving a stale nx=True.

Related errors


AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04). Data as JSON: /data/errors/d921575f880ce8bc.json. Report an issue: GitHub.