XADD

Summary: Appends a new message to a stream. Creates the key if it doesn't exist.

Required arguments

key

The name of the stream key.

id

The stream entry ID. Use `*` to auto-generate a unique ID, or specify a well-formed ID in the format `<ms>-<seq>` (for example, `1526919030474-55`).

field value [field value ...]

One or more field-value pairs that make up the stream entry. You must provide at least one field-value pair.

Optional arguments

NOMKSTREAM

Prevents the creation of a new stream if the key does not exist. Available since Redis 6.2.0.

IDMPAUTO producer-id | IDMP producer-id idempotent-id

Enables idempotent message processing (at-most-once production) to prevent duplicate entries. Available since Redis 8.6.

- `IDMPAUTO producer-id`: Automatically generates a unique idempotent ID (iid) for the specified producer-id. Redis tracks this iid to prevent duplicate messages from the same producer-id.
- `IDMP producer-id idempotent-id`: Uses the specified idempotent-id for the given producer-id. If this producer-id/idempotent-id combination was already used, the command returns the ID of the original entry instead of creating a duplicate.

The producer-id identifies the source of the message, while the idempotent-id ensures uniqueness within that producer-id's message stream. Redis maintains an internal map of recent producer-id/idempotent-id combinations to detect and prevent duplicates.

Both modes can only be specified when the entry ID is `*` (auto-generated).

Use [`XCFGSET`]() to configure how long idempotent IDs are retained (`IDMP-DURATION`) and the maximum number tracked per producer (`IDMP-MAXSIZE`).

See [Idempotent message processing]() for more information.

KEEPREF | DELREF | ACKED

Specifies how to handle consumer group references when trimming. If there are no consumer groups, these arguments have no effect. Available since Redis 8.2.

If no option is specified, `KEEPREF` is used by default. Unlike the `XDELEX` and `XACKDEL` commands where one of these options is required, here they are optional to maintain backward compatibility:

- `KEEPREF` (default): When trimming, removes entries from the stream according to the specified strategy (`MAXLEN` or `MINID`), regardless of whether they are referenced by any consumer groups, but preserves existing references to these entries in all consumer groups' PEL (Pending Entries List).
- `DELREF`: When trimming, removes entries from the stream according to the specified strategy and also removes all references to these entries from all consumer groups' PEL.
- `ACKED`: When trimming, only removes entries that were read and acknowledged by all consumer groups. Note that if the number of referenced entries is larger than `MAXLEN`, trimming will still stop at the limit.

MAXLEN | MINID [= | ~] threshold [LIMIT count]>

Trims the stream to maintain a specific size or remove old entries:

MAXLEN | MINID

The trimming strategy:
- `MAXLEN`: Evicts entries as long as the stream's length exceeds the specified threshold
- `MINID`: Evicts entries with IDs lower than the specified threshold (available since Redis 6.2.0)

= | ~

The trimming operator:
- `=`: Exact trimming (default) - trims to the exact threshold
- `~`: Approximate trimming - more efficient, may leave slightly more entries than the threshold

threshold

The trimming threshold:
- For `MAXLEN`: `threshold` is a non-negative integer specifying the maximum number of entries that may remain in the stream after trimming. Redis enforces this by removing the oldest entries - that is, the entries with the lowest stream IDs - so that only the newest entries are kept.
- For `MINID`: `threshold` is a stream ID. All entries whose IDs are less than `threshold` are trimmed. All entries with IDs greater than or equal to `threshold` are kept.

LIMIT count

Limits the number of entries to examine during trimming. Available since Redis 6.2.0. When not specified, Redis uses a default value of 100 * the number of entries in a macro node. Specifying 0 disables the limiting mechanism entirely.

Each entry consists of a list of field-value pairs.
Redis stores the field-value pairs in the same order you provide them.
Commands that read the stream, such as [`XRANGE`]() or [`XREAD`](), return the fields and values in exactly the same order you added them with `XADD`.

`XADD` is the only Redis command that can add data to a stream. However,
other commands, such as [`XDEL`]() and [`XTRIM`](), can
remove data from a stream.

Specifying a Stream ID as an argument

A stream entry ID identifies a specific entry inside a stream.

`XADD` auto-generates a unique ID for you if you specify the `*` character (asterisk) as the ID argument. However, you can also specify a well-formed ID to add the new entry with that exact ID, though this is useful only in rare cases.

Specify IDs using two numbers separated by a `-` character:

    1526919030474-55

Both numbers are 64-bit integers. When Redis auto-generates an ID, the
first part is the Unix time in milliseconds of the Redis instance generating
the ID. The second part is a sequence number used to
distinguish IDs generated in the same millisecond.

You can also specify an incomplete ID that consists only of the milliseconds part, which Redis interprets as a zero value for the sequence part.
To have only the sequence part automatically generated, specify the milliseconds part followed by the `-` separator and the `*` character:

[code example]

Redis guarantees that IDs are always incremental: the ID of any entry you insert will be greater than any previous ID, so entries are totally ordered inside a stream. To guarantee this property, if the current top ID in the stream has a time greater than the current local time of the instance, Redis uses the top entry time instead and increments the sequence part of the ID. This may happen when, for instance, the local clock jumps backward, or after a failover when the new master has a different absolute time.

When you specify an explicit ID to `XADD`, the minimum valid ID is `0-1`, and you *must* specify an ID that is greater than any other ID currently inside the stream, otherwise the command fails and returns an error. Specifying explicit IDs is usually useful only if you have another system generating unique IDs (for instance an SQL table) and you want the Redis stream IDs to match those from your other system.

Capped streams

`XADD` incorporates the same semantics as the [`XTRIM`]() command - refer to its documentation page for more information.
This allows you to add new entries and keep the stream's size in check with a single call to `XADD`, effectively capping the stream with an arbitrary threshold.
Although exact trimming is possible and is the default, due to the internal representation of streams, it is more efficient to add an entry and trim the stream with `XADD` using **almost exact** trimming (the `~` argument).

For example, calling `XADD` in the following form:

    XADD mystream MAXLEN ~ 1000 * ... entry fields here ...

This adds a new entry but also evicts old entries so that the stream contains only 1000 entries, or at most a few tens more.

Additional information about streams

For more information about Redis streams, see the
[introduction to Redis Streams document]().

Examples

#### Code Examples

Basic XADD: Add entries to a stream with auto-generated IDs, check stream the stream size, and read entries

**Difficulty:** Beginner

**Commands:** XADD, XLEN, XRANGE

**Complexity:**
- XADD: O(1)
- XLEN: O(1)
- XRANGE: O(N)

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), PHP, Python, Rust (Asynchronous), Rust (Synchronous)

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**PHP:**

[code example]

**Python:**

[code example]

**Rust (Asynchronous):**

[code example]

**Rust (Synchronous):**

[code example]

Idempotent message processing examples

#### Code Examples

Idempotent XADD (Redis 8.6+): Use IDMP and IDMPAUTO for at-most-once message delivery, preventing duplicate entries

**Difficulty:** Intermediate

**Commands:** XADD, XCFGSET

**Complexity:**
- XADD: O(1)
- XCFGSET: O(1)

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), PHP, Python, Rust (Asynchronous), Rust (Synchronous)

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**PHP:**

[code example]

**Python:**

[code example]

**Rust (Asynchronous):**

[code example]

**Rust (Synchronous):**

[code example]

| Redis
Software | Redis
Cloud | Notes |
|:----------------------|:-----------------|:------|
| ✅ Standard
<nobr>✅ Active-Active</nobr> | ✅ Standard
<nobr>✅ Active-Active</nobr> |  |

Return information

**RESP2:**

One of the following:
* [Bulk string reply](../../develop/reference/protocol-spec#bulk-strings): The ID of the added entry. The ID is the one automatically generated if an asterisk (`*`) is passed as the _id_ argument, otherwise the command just returns the same ID specified by the user during insertion. When using IDMP and a duplicate is detected, returns the ID of the original entry.
* [Nil reply](../../develop/reference/protocol-spec#bulk-strings): if the NOMKSTREAM option is given and the key doesn't exist.

**RESP3:**

One of the following:
* [Bulk string reply](../../develop/reference/protocol-spec#bulk-strings): The ID of the added entry. The ID is the one automatically generated if an asterisk (`*`) is passed as the _id_ argument, otherwise the command just returns the same ID specified by the user during insertion. When using IDMP and a duplicate is detected, returns the ID of the original entry.
* [Null reply](../../develop/reference/protocol-spec#nulls): if the NOMKSTREAM option is given and the key doesn't exist.

> XADD mystream * name Sara surname OConnor
4378417975-0"
> XADD mystream * field1 value1 field2 value2 field3 value3
4378417976-0"
> XLEN mystream
eger) 2
> XRANGE mystream - +
1) 1) "1774378417975-0"
   2) 1) "name"
      2) "Sara"
      3) "surname"
      4) "OConnor"
2) 1) "1774378417976-0"
   2) 1) "field1"
      2) "value1"
      3) "field2"
      4) "value2"
      5) "field3"
      6) "value3"

var res1 = db.StreamAdd("mystream", new NameValueEntry[] {
            new NameValueEntry("name", "Sara"),
            new NameValueEntry("surname", "OConnor")
        });
        Console.WriteLine(res1);  // >>> 1726055713866-0

        var res2 = db.StreamAdd("mystream", new NameValueEntry[] {
            new NameValueEntry("field1", "value1"),
            new NameValueEntry("field2", "value2"),
            new NameValueEntry("field3", "value3")
        });
        Console.WriteLine(res2);  // >>> 1726055713866-1

        var res3 = db.StreamLength("mystream");
        Console.WriteLine(res3);  // >>> 2

        var res4 = db.StreamRange("mystream", "-", "+");
        foreach (var entry in res4)
        {
            Console.WriteLine($"{entry.Id} -> {string.Join(", ", entry.Values.Select(v => $"{v.Name}={v.Value}"))}");
        }
        // >>> 1726055713866-0 -> name=Sara, surname=OConnor
        // >>> 1726055713866-1 -> field1=value1, field2=value2, field3=value3

res1, err := rdb.XAdd(ctx, &redis.XAddArgs{
		Stream: "mystream",
		Values: map[string]interface{}{
			"name":    "Sara",
			"surname": "OConnor",
		},
	}).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res1 != "") // >>> true

	res2, err := rdb.XAdd(ctx, &redis.XAddArgs{
		Stream: "mystream",
		Values: map[string]interface{}{
			"field1": "value1",
			"field2": "value2",
			"field3": "value3",
		},
	}).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res2 != "") // >>> true

	res3, err := rdb.XLen(ctx, "mystream").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res3) // >>> 2

	res4, err := rdb.XRange(ctx, "mystream", "-", "+").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(len(res4)) // >>> 2

Map<String, String> entry1 = new HashMap<>();
            entry1.put("name", "Sara");
            entry1.put("surname", "OConnor");

            CompletableFuture<Void> xadd1Ops = asyncCommands.xadd("mystream", entry1)
                .thenCompose(res1 -> {
                    System.out.println(res1); // >>> 1726055713866-0
                    Map<String, String> entry2 = new HashMap<>();
                    entry2.put("field1", "value1");
                    entry2.put("field2", "value2");
                    entry2.put("field3", "value3");
                    return asyncCommands.xadd("mystream", entry2);
                })
                .thenCompose(res2 -> {
                    System.out.println(res2); // >>> 1726055713866-1
                    return asyncCommands.xlen("mystream");
                })
                .thenCompose(res3 -> {
                    System.out.println(res3); // >>> 2
                    return asyncCommands.xrange("mystream", Range.unbounded());
                })
                .thenAccept(res4 -> {
                    for (var entry : res4) {
                        System.out.println(entry.getId() + " -> " + entry.getBody());
                    }
                    // >>> 1726055713866-0 -> {name=Sara, surname=OConnor}
                    // >>> 1726055713866-1 -> {field1=value1, field2=value2, field3=value3}
                })
                .toCompletableFuture();

            xadd1Ops.join();

Map<String, String> entry1 = new HashMap<>();
            entry1.put("name", "Sara");
            entry1.put("surname", "OConnor");

            Mono<String> addEntry1 = reactiveCommands.xadd("mystream", entry1)
                .doOnNext(res1 -> {
                    System.out.println(res1); // >>> 1726055713866-0
                });

            addEntry1.block();

            Map<String, String> entry2 = new HashMap<>();
            entry2.put("field1", "value1");
            entry2.put("field2", "value2");
            entry2.put("field3", "value3");

            Mono<String> addEntry2 = reactiveCommands.xadd("mystream", entry2)
                .doOnNext(res2 -> {
                    System.out.println(res2); // >>> 1726055713866-1
                });

            addEntry2.block();

            Mono<Long> getLen = reactiveCommands.xlen("mystream")
                .doOnNext(res3 -> {
                    System.out.println(res3); // >>> 2
                });

            getLen.block();

            Mono<List<StreamMessage<String, String>>> getRange = reactiveCommands
                .xrange("mystream", Range.unbounded())
                .collectList()
                .doOnNext(res4 -> {
                    for (var entry : res4) {
                        System.out.println(entry.getId() + " -> " + entry.getBody());
                    }
                    // >>> 1726055713866-0 -> {name=Sara, surname=OConnor}
                    // >>> 1726055713866-1 -> {field1=value1, field2=value2, field3=value3}
                });

            getRange.block();

Map<String, String> entry1 = new HashMap<>();
        entry1.put("name", "Sara");
        entry1.put("surname", "OConnor");
        StreamEntryID res1 = jedis.xadd("mystream", entry1, XAddParams.xAddParams());
        System.out.println(res1); // >>> 1726055713866-0

        Map<String, String> entry2 = new HashMap<>();
        entry2.put("field1", "value1");
        entry2.put("field2", "value2");
        entry2.put("field3", "value3");
        StreamEntryID res2 = jedis.xadd("mystream", entry2, XAddParams.xAddParams());
        System.out.println(res2); // >>> 1726055713866-1

        long res3 = jedis.xlen("mystream");
        System.out.println(res3); // >>> 2

        List<StreamEntry> res4 = jedis.xrange("mystream", "-", "+");
        for (StreamEntry entry : res4) {
            System.out.println(entry.getID() + " -> " + entry.getFields());
        }
        // >>> 1726055713866-0 -> {name=Sara, surname=OConnor}
        // >>> 1726055713866-1 -> {field1=value1, field2=value2, field3=value3}

const res1 = await client.xAdd('mystream', '*', {
  'name': 'Sara',
  'surname': 'OConnor'
});
console.log(res1); // >>> 1726055713866-0

const res2 = await client.xAdd('mystream', '*', {
  'field1': 'value1',
  'field2': 'value2',
  'field3': 'value3'
});
console.log(res2); // >>> 1726055713866-1

const res3 = await client.xLen('mystream');
console.log(res3); // >>> 2

const res4 = await client.xRange('mystream', '-', '+');
console.log(res4);
// >>> [
//   { id: '1726055713866-0', message: { name: 'Sara', surname: 'OConnor' } },
//   { id: '1726055713866-1', message: { field1: 'value1', field2: 'value2', field3: 'value3' } }
// ]

$res1 = $redis->xadd('mystream', ['name' => 'Sara', 'surname' => 'OConnor']);
        echo $res1 . PHP_EOL; // >>> 1726055713866-0

        $res2 = $redis->xadd('mystream', ['field1' => 'value1', 'field2' => 'value2', 'field3' => 'value3']);
        echo $res2 . PHP_EOL; // >>> 1726055713866-1

        $res3 = $redis->xlen('mystream');
        echo $res3 . PHP_EOL; // >>> 2

        $res4 = $redis->xrange('mystream', '-', '+');
        foreach ($res4 as $id => $fields) {
            echo $id . ' -> ' . json_encode($fields) . PHP_EOL;
        }
        // >>> 1726055713866-0 -> {"name":"Sara","surname":"OConnor"}
        // >>> 1726055713866-1 -> {"field1":"value1","field2":"value2","field3":"value3"}

let client = redis::Client::open("redis://127.0.0.1/").unwrap();
        let mut con = client.get_multiplexed_async_connection().await.unwrap();

        let res1: String = redis::cmd("XADD")
            .arg("mystream")
            .arg("*")
            .arg("name")
            .arg("Sara")
            .arg("surname")
            .arg("OConnor")
            .query_async(&mut con)
            .await
            .unwrap();
        println!("XADD result: {}", res1); // >>> 1726055713866-0

        let res2: String = redis::cmd("XADD")
            .arg("mystream")
            .arg("*")
            .arg("field1")
            .arg("value1")
            .arg("field2")
            .arg("value2")
            .arg("field3")
            .arg("value3")
            .query_async(&mut con)
            .await
            .unwrap();
        println!("XADD result: {}", res2); // >>> 1726055713866-1

        let res3: i64 = con.xlen("mystream").await.unwrap();
        println!("XLEN result: {}", res3); // >>> 2

        let res4: redis::streams::StreamRangeReply = con.xrange_all("mystream").await.unwrap();
        for entry in &res4.ids {
            println!("{} -> {:?}", entry.id, entry.map);
        }
        // >>> 1726055713866-0 -> {"name": "Sara", "surname": "OConnor"}
        // >>> 1726055713866-1 -> {"field1": "value1", "field2": "value2", "field3": "value3"}

let client = redis::Client::open("redis://127.0.0.1/").unwrap();
        let mut con = client.get_connection().unwrap();

        let res1: String = redis::cmd("XADD")
            .arg("mystream")
            .arg("*")
            .arg("name")
            .arg("Sara")
            .arg("surname")
            .arg("OConnor")
            .query(&mut con)
            .unwrap();
        println!("XADD result: {}", res1); // >>> 1726055713866-0

        let res2: String = redis::cmd("XADD")
            .arg("mystream")
            .arg("*")
            .arg("field1")
            .arg("value1")
            .arg("field2")
            .arg("value2")
            .arg("field3")
            .arg("value3")
            .query(&mut con)
            .unwrap();
        println!("XADD result: {}", res2); // >>> 1726055713866-1

        let res3: i64 = con.xlen("mystream").unwrap();
        println!("XLEN result: {}", res3); // >>> 2

        let res4: redis::streams::StreamRangeReply = con.xrange_all("mystream").unwrap();
        for entry in &res4.ids {
            println!("{} -> {:?}", entry.id, entry.map);
        }
        // >>> 1726055713866-0 -> {"name": "Sara", "surname": "OConnor"}
        // >>> 1726055713866-1 -> {"field1": "value1", "field2": "value2", "field3": "value3"}

> XADD mystream IDMP producer1 msg1 * field value
"1774378417976-0"
> XADD mystream IDMP producer1 msg1 * field different_value
"1774378417976-0"
> XADD mystream IDMPAUTO producer2 * field value
"1774378417977-0"
> XADD mystream IDMPAUTO producer2 * field value
"1774378417977-0"
> XCFGSET mystream IDMP-DURATION 300 IDMP-MAXSIZE 1000
"OK"

// Note: IDMP is a Redis 8.6 feature - using Execute for raw command access
        var res5 = db.Execute("XADD", "mystream", "IDMP", "producer1", "msg1", "*", "field", "value");
        Console.WriteLine(res5);  // >>> 1726055713867-0

        // Attempting to add the same message again with IDMP returns the original entry ID
        var res6 = db.Execute("XADD", "mystream", "IDMP", "producer1", "msg1", "*", "field", "different_value");
        Console.WriteLine(res6);  // >>> 1726055713867-0 (same ID as res5, message was deduplicated)

        var res7 = db.Execute("XADD", "mystream", "IDMPAUTO", "producer2", "*", "field", "value");
        Console.WriteLine(res7);  // >>> 1726055713867-1

        // Auto-generated idempotent ID prevents duplicates for same producer+content
        var res8 = db.Execute("XADD", "mystream", "IDMPAUTO", "producer2", "*", "field", "value");
        Console.WriteLine(res8);  // >>> 1726055713867-1 (same ID as res7, duplicate detected)

        // Configure idempotent message processing settings
        var res9 = db.Execute("XCFGSET", "mystream", "IDMP-DURATION", "300", "IDMP-MAXSIZE", "1000");
        Console.WriteLine(res9);  // >>> OK

res5, err := rdb.XAdd(ctx, &redis.XAddArgs{
		Stream:       "mystream",
		Values:       map[string]interface{}{"field": "value"},
		ProducerID:   "producer1",
		IdempotentID: "msg1",
	}).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res5 != "") // >>> true

	// Attempting to add the same message again with IDMP returns the original entry ID
	res6, err := rdb.XAdd(ctx, &redis.XAddArgs{
		Stream:       "mystream",
		Values:       map[string]interface{}{"field": "different_value"},
		ProducerID:   "producer1",
		IdempotentID: "msg1",
	}).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res5 == res6) // >>> true (same ID, message was deduplicated)

	res7, err := rdb.XAdd(ctx, &redis.XAddArgs{
		Stream:         "mystream",
		Values:         map[string]interface{}{"field": "value"},
		ProducerID:     "producer2",
		IdempotentAuto: true,
	}).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res7 != "") // >>> true

	// Auto-generated idempotent ID prevents duplicates for same producer+content
	res8, err := rdb.XAdd(ctx, &redis.XAddArgs{
		Stream:         "mystream",
		Values:         map[string]interface{}{"field": "value"},
		ProducerID:     "producer2",
		IdempotentAuto: true,
	}).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res7 == res8) // >>> true (same ID, duplicate detected)

	// Configure idempotent message processing settings
	res9, err := rdb.XCfgSet(ctx, &redis.XCfgSetArgs{
		Stream:   "mystream",
		Duration: 300,  // 300 seconds
		MaxSize:  1000, // 1000 idempotent IDs
	}).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res9) // >>> OK

Map<String, String> idmpEntry1 = new HashMap<>();
            idmpEntry1.put("field", "value");

            CompletableFuture<Void> xadd2Ops = asyncCommands.xadd(
                    "mystream",
                    XAddArgs.Builder.idmp("producer1", "msg1"),
                    idmpEntry1)
                .thenCompose(res5 -> {
                    System.out.println(res5); // >>> 1726055713867-0
                    // Attempting to add same message again with IDMP returns original entry ID
                    Map<String, String> idmpEntry2 = new HashMap<>();
                    idmpEntry2.put("field", "different_value");
                    return asyncCommands.xadd("mystream",
                        XAddArgs.Builder.idmp("producer1", "msg1"), idmpEntry2);
                })
                .thenCompose(res6 -> {
                    System.out.println(res6); // >>> 1726055713867-0 (deduplicated)
                    Map<String, String> idmpAutoEntry1 = new HashMap<>();
                    idmpAutoEntry1.put("field", "value");
                    return asyncCommands.xadd("mystream",
                        XAddArgs.Builder.idmpAuto("producer2"), idmpAutoEntry1);
                })
                .thenCompose(res7 -> {
                    System.out.println(res7); // >>> 1726055713867-1
                    Map<String, String> idmpAutoEntry2 = new HashMap<>();
                    idmpAutoEntry2.put("field", "value");
                    return asyncCommands.xadd("mystream",
                        XAddArgs.Builder.idmpAuto("producer2"), idmpAutoEntry2);
                })
                .thenAccept(res8 -> {
                    System.out.println(res8); // >>> 1726055713867-1 (duplicate detected)
                })
                .toCompletableFuture();

            xadd2Ops.join();

Map<String, String> idmpEntry1 = new HashMap<>();
            idmpEntry1.put("field", "value");

            Mono<String> addIdmp1 = reactiveCommands.xadd(
                    "mystream",
                    XAddArgs.Builder.idmp("producer1", "msg1"),
                    idmpEntry1)
                .doOnNext(res5 -> {
                    System.out.println(res5); // >>> 1726055713867-0
                });

            addIdmp1.block();

            // Attempting to add the same message again with IDMP returns the original entry ID
            Map<String, String> idmpEntry2 = new HashMap<>();
            idmpEntry2.put("field", "different_value");

            Mono<String> addIdmp2 = reactiveCommands.xadd(
                    "mystream",
                    XAddArgs.Builder.idmp("producer1", "msg1"),
                    idmpEntry2)
                .doOnNext(res6 -> {
                    System.out.println(res6); // >>> 1726055713867-0 (deduplicated)
                });

            addIdmp2.block();

            Map<String, String> idmpAutoEntry1 = new HashMap<>();
            idmpAutoEntry1.put("field", "value");

            Mono<String> addIdmpAuto1 = reactiveCommands.xadd(
                    "mystream",
                    XAddArgs.Builder.idmpAuto("producer2"),
                    idmpAutoEntry1)
                .doOnNext(res7 -> {
                    System.out.println(res7); // >>> 1726055713867-1
                });

            addIdmpAuto1.block();

            // Auto-generated idempotent ID prevents duplicates for same producer+content
            Map<String, String> idmpAutoEntry2 = new HashMap<>();
            idmpAutoEntry2.put("field", "value");

            Mono<String> addIdmpAuto2 = reactiveCommands.xadd(
                    "mystream",
                    XAddArgs.Builder.idmpAuto("producer2"),
                    idmpAutoEntry2)
                .doOnNext(res8 -> {
                    System.out.println(res8); // >>> 1726055713867-1 (duplicate detected)
                });

            addIdmpAuto2.block();

Map<String, String> idmpEntry1 = new HashMap<>();
        idmpEntry1.put("field", "value");
        StreamEntryID res5 = jedis.xadd("mystream", idmpEntry1,
            XAddParams.xAddParams().idmp("producer1", "msg1"));
        System.out.println(res5); // >>> 1726055713867-0

        // Attempting to add the same message again with IDMP returns the original entry ID
        Map<String, String> idmpEntry2 = new HashMap<>();
        idmpEntry2.put("field", "different_value");
        StreamEntryID res6 = jedis.xadd("mystream", idmpEntry2,
            XAddParams.xAddParams().idmp("producer1", "msg1"));
        System.out.println(res6); // >>> 1726055713867-0 (same ID as res5, message was deduplicated)

        Map<String, String> idmpAutoEntry1 = new HashMap<>();
        idmpAutoEntry1.put("field", "value");
        StreamEntryID res7 = jedis.xadd("mystream", idmpAutoEntry1,
            XAddParams.xAddParams().idmpAuto("producer2"));
        System.out.println(res7); // >>> 1726055713867-1

        // Auto-generated idempotent ID prevents duplicates for same producer+content
        Map<String, String> idmpAutoEntry2 = new HashMap<>();
        idmpAutoEntry2.put("field", "value");
        StreamEntryID res8 = jedis.xadd("mystream", idmpAutoEntry2,
            XAddParams.xAddParams().idmpAuto("producer2"));
        System.out.println(res8); // >>> 1726055713867-1 (same ID as res7, duplicate detected)

        // Configure idempotent message processing settings
        String res9 = jedis.xcfgset("mystream",
            XCfgSetParams.xCfgSetParams().idmpDuration(300).idmpMaxsize(1000));
        System.out.println(res9); // >>> OK

const res5 = await client.xAdd('mystream', '*', { 'field': 'value' }, {
  IDMP: { pid: 'producer1', iid: 'msg1' }
});
console.log(res5); // >>> 1726055713867-0

// Attempting to add the same message again with IDMP returns the original entry ID
const res6 = await client.xAdd('mystream', '*', { 'field': 'different_value' }, {
  IDMP: { pid: 'producer1', iid: 'msg1' }
});
console.log(res6); // >>> 1726055713867-0 (same ID as res5, message was deduplicated)

const res7 = await client.xAdd('mystream', '*', { 'field': 'value' }, {
  IDMPAUTO: { pid: 'producer2' }
});
console.log(res7); // >>> 1726055713867-1

// Auto-generated idempotent ID prevents duplicates for same producer+content
const res8 = await client.xAdd('mystream', '*', { 'field': 'value' }, {
  IDMPAUTO: { pid: 'producer2' }
});
console.log(res8); // >>> 1726055713867-1 (same ID as res7, duplicate detected)

// Configure idempotent message processing settings
const res9 = await client.xCfgSet('mystream', {
  IDMP_DURATION: 300,
  IDMP_MAXSIZE: 1000
});
console.log(res9); // >>> OK

$res5 = $redis->xadd('mystream', ['field' => 'value'], '*', ['idmp' => ['producer1', 'msg1']]);
        echo $res5 . PHP_EOL; // >>> 1726055713867-0

        // Attempting to add the same message again with IDMP returns the original entry ID
        $res6 = $redis->xadd('mystream', ['field' => 'different_value'], '*', ['idmp' => ['producer1', 'msg1']]);
        echo $res6 . PHP_EOL; // >>> 1726055713867-0 (same ID as res5, message was deduplicated)

        $res7 = $redis->xadd('mystream', ['field' => 'value'], '*', ['idmpauto' => 'producer2']);
        echo $res7 . PHP_EOL; // >>> 1726055713867-1

        // Auto-generated idempotent ID prevents duplicates for same producer+content
        $res8 = $redis->xadd('mystream', ['field' => 'value'], '*', ['idmpauto' => 'producer2']);
        echo $res8 . PHP_EOL; // >>> 1726055713867-1 (same ID as res7, duplicate detected)

        // Configure idempotent message processing settings
        $res9 = $redis->xcfgset('mystream', 300, 1000);
        echo $res9 . PHP_EOL; // >>> OK

res5 = r.xadd("mystream", {"field": "value"}, idmp=("producer1", b"msg1"))
print(res5)  # >>> 1726055713867-0

# Attempting to add the same message again with IDMP returns the original entry ID
res6 = r.xadd("mystream", {"field": "different_value"}, idmp=("producer1", b"msg1"))
print(res6)  # >>> 1726055713867-0 (same ID as res5, message was deduplicated)

res7 = r.xadd("mystream", {"field": "value"}, idmpauto="producer2")
print(res7)  # >>> 1726055713867-1

# Auto-generated idempotent ID prevents duplicates for same producer+content
res8 = r.xadd("mystream", {"field": "value"}, idmpauto="producer2")
print(res8)  # >>> 1726055713867-1 (same ID as res7, duplicate detected)

# Configure idempotent message processing settings
res9 = r.xcfgset("mystream", idmp_duration=300, idmp_maxsize=1000)
print(res9)  # >>> True

// Note: IDMP is a Redis 8.6 feature - using raw commands
        let res5: String = redis::cmd("XADD")
            .arg("idmpstream")
            .arg("IDMP")
            .arg("producer1")
            .arg("msg1")
            .arg("*")
            .arg("field")
            .arg("value")
            .query_async(&mut con)
            .await
            .unwrap();
        println!("XADD IDMP result: {}", res5); // >>> 1726055713867-0

        // Attempting to add the same message again with IDMP returns the original entry ID
        let res6: String = redis::cmd("XADD")
            .arg("idmpstream")
            .arg("IDMP")
            .arg("producer1")
            .arg("msg1")
            .arg("*")
            .arg("field")
            .arg("different_value")
            .query_async(&mut con)
            .await
            .unwrap();
        println!("XADD IDMP result: {}", res6); // >>> 1726055713867-0 (deduplicated)

        let res7: String = redis::cmd("XADD")
            .arg("idmpstream")
            .arg("IDMPAUTO")
            .arg("producer2")
            .arg("*")
            .arg("field")
            .arg("value")
            .query_async(&mut con)
            .await
            .unwrap();
        println!("XADD IDMPAUTO result: {}", res7); // >>> 1726055713867-1

        // Auto-generated idempotent ID prevents duplicates for same producer+content
        let res8: String = redis::cmd("XADD")
            .arg("idmpstream")
            .arg("IDMPAUTO")
            .arg("producer2")
            .arg("*")
            .arg("field")
            .arg("value")
            .query_async(&mut con)
            .await
            .unwrap();
        println!("XADD IDMPAUTO result: {}", res8); // >>> 1726055713867-1 (duplicate detected)

        // Configure idempotent message processing settings
        let res9: String = redis::cmd("XCFGSET")
            .arg("idmpstream")
            .arg("IDMP-DURATION")
            .arg("300")
            .arg("IDMP-MAXSIZE")
            .arg("1000")
            .query_async(&mut con)
            .await
            .unwrap();
        println!("XCFGSET result: {}", res9); // >>> OK

// Note: IDMP is a Redis 8.6 feature - using raw commands
        let res5: String = redis::cmd("XADD")
            .arg("idmpstream")
            .arg("IDMP")
            .arg("producer1")
            .arg("msg1")
            .arg("*")
            .arg("field")
            .arg("value")
            .query(&mut con)
            .unwrap();
        println!("XADD IDMP result: {}", res5); // >>> 1726055713867-0

        // Attempting to add the same message again with IDMP returns the original entry ID
        let res6: String = redis::cmd("XADD")
            .arg("idmpstream")
            .arg("IDMP")
            .arg("producer1")
            .arg("msg1")
            .arg("*")
            .arg("field")
            .arg("different_value")
            .query(&mut con)
            .unwrap();
        println!("XADD IDMP result: {}", res6); // >>> 1726055713867-0 (deduplicated)

        let res7: String = redis::cmd("XADD")
            .arg("idmpstream")
            .arg("IDMPAUTO")
            .arg("producer2")
            .arg("*")
            .arg("field")
            .arg("value")
            .query(&mut con)
            .unwrap();
        println!("XADD IDMPAUTO result: {}", res7); // >>> 1726055713867-1

        // Auto-generated idempotent ID prevents duplicates for same producer+content
        let res8: String = redis::cmd("XADD")
            .arg("idmpstream")
            .arg("IDMPAUTO")
            .arg("producer2")
            .arg("*")
            .arg("field")
            .arg("value")
            .query(&mut con)
            .unwrap();
        println!("XADD IDMPAUTO result: {}", res8); // >>> 1726055713867-1 (duplicate detected)

        // Configure idempotent message processing settings
        let res9: String = redis::cmd("XCFGSET")
            .arg("idmpstream")
            .arg("IDMP-DURATION")
            .arg("300")
            .arg("IDMP-MAXSIZE")
            .arg("1000")
            .query(&mut con)
            .unwrap();
        println!("XCFGSET result: {}", res9); // >>> OK


XAUTOCLAIM

Summary: Changes, or acquires, ownership of messages in a consumer group, as if the messages were delivered to as consumer group member.

Overview

This command transfers ownership of pending stream entries that match the specified criteria. Conceptually, `XAUTOCLAIM`  is equivalent to calling [`XPENDING`]() and then [`XCLAIM`](),
but provides a more straightforward way to deal with message delivery failures via [`SCAN`]()-like semantics.

Like [`XCLAIM`](), the command operates on the stream entries at `<key>` and in the context of the provided `<group>`.
It transfers ownership to `<consumer>` of messages pending for more than `<min-idle-time>` milliseconds and having an equal or greater ID than `<start>`.

The optional `<count>` argument, which defaults to 100, is the upper limit of the number of entries that the command attempts to claim.
Internally, the command begins scanning the consumer group's Pending Entries List (PEL) from `<start>` and filters out entries having an idle time less than or equal to `<min-idle-time>`.
The maximum number of pending entries that the command scans is the product of multiplying `<count>`'s value by 10 (hard-coded).
It is possible, therefore, that the number of entries claimed will be less than the specified value.

The optional `JUSTID` argument changes the reply to return just an array of IDs of messages successfully claimed, without returning the actual message.
Using this option means the retry counter is not incremented.

The command returns the claimed entries as an array. It also returns a stream ID intended for cursor-like use as the `<start>` argument for its subsequent call.
When there are no remaining PEL entries, the command returns the special `0-0` ID to signal completion.
However, note that you may want to continue calling `XAUTOCLAIM` even after the scan is complete with the `0-0` as `<start>` ID, because enough time passed, so older pending entries may now be eligible for claiming.

Note that only messages that are idle longer than `<min-idle-time>` are claimed, and claiming a message resets its idle time.
This ensures that only a single consumer can successfully claim a given pending message at a specific instant of time and trivially reduces the probability of processing the same message multiple times.

While iterating the PEL, if `XAUTOCLAIM` stumbles upon a message which doesn't exist in the stream anymore (either trimmed or deleted by [`XDEL`]()) it does not claim it, and deletes it from the PEL in which it was found. This feature was introduced in Redis 7.0.
These message IDs are returned to the caller as a part of `XAUTOCLAIM`s reply.

Lastly, claiming a message with `XAUTOCLAIM` also increments the attempted deliveries count for that message, unless the `JUSTID` option has been specified (which only delivers the message ID, not the message itself).
Messages that cannot be processed for some reason - for example, because consumers systematically crash when processing them - will exhibit high attempted delivery counts that can be detected by monitoring.

Examples

[code example]

In the above example, we attempt to claim up to 25 entries that are pending and idle (not having been acknowledged or claimed) for at least an hour, starting at the stream's beginning.
The consumer "Alice" from the "mygroup" group acquires ownership of these messages.
Note that the stream ID returned in the example is `0-0`, indicating that the entire stream was scanned.
We can also see that `XAUTOCLAIM` did not stumble upon any deleted messages (the third reply element is an empty array).

Redis Software and Redis Cloud compatibility

Return information

**RESP2:**

[Array reply](../../develop/reference/protocol-spec#arrays), specifically, an array with three elements:
1. A stream ID to be used as the _start_ argument for the next call to XAUTOCLAIM.
2. An [Array reply](../../develop/reference/protocol-spec#arrays) containing all the successfully claimed messages in the same format as `XRANGE`.
3. An [Array reply](../../develop/reference/protocol-spec#arrays) containing message IDs that no longer exist in the stream, and were deleted from the PEL in which they were found.

**RESP3:**

[Array reply](../../develop/reference/protocol-spec#arrays), specifically, an array with three elements:
1. A stream ID to be used as the _start_ argument for the next call to XAUTOCLAIM.
2. An [Array reply](../../develop/reference/protocol-spec#arrays) containing all the successfully claimed messages in the same format as `XRANGE`.
3. An [Array reply](../../develop/reference/protocol-spec#arrays) containing message IDs that no longer exist in the stream, and were deleted from the PEL in which they were found.


XCFGSET

Summary: Sets the IDMP configuration parameters for a stream.

Overview

Sets the IDMP (Idempotent Message Processing) configuration parameters for a stream. This command configures how long idempotent IDs are retained and the maximum number of idempotent IDs tracked per producer.

Required arguments

key

The name of the stream key. The stream must already exist.

Optional arguments

IDMP-DURATION idmp-duration

Sets the duration in seconds that each idempotent ID (iid) is kept in the stream's IDMP map. Valid range: 1-86,400 seconds. Default: 100 seconds.

When an idempotent ID expires, it can be reused for new messages. This provides an operational guarantee that Redis will not forget an idempotency ID before the duration elapses (unless capacity is reached).

IDMP-MAXSIZE idmp-maxsize

Sets the maximum number of most recent idempotent IDs kept for each producer in the stream's IDMP map. Valid range: 1-10,000 entries. Default: 100 entries.

When the capacity is reached, the oldest idempotent IDs for that producer are evicted regardless of remaining duration. This prevents unbounded memory growth.

Behavior

- Calling `XCFGSET` clears all existing producer IDMP maps for the stream.
- At least one of `IDMP-DURATION` or `IDMP-MAXSIZE` must be specified.
- The stream must exist before calling this command.
- Configuration changes apply immediately to all future IDMP operations.

Examples

[code example]

| Redis
Software | Redis
Cloud | Notes |
|:----------------------|:-----------------|:------|
| ❌ Standard
<nobr>❌ Active-Active</nobr> | ❌ Standard
<nobr>❌ Active-Active</nobr> |  |

Return information

**RESP2:**

[Simple string reply](../../develop/reference/protocol-spec#simple-strings): `OK` if the configuration was set successfully.

**RESP3:**

[Simple string reply](../../develop/reference/protocol-spec#simple-strings): `OK` if the configuration was set successfully.

Error conditions

The command returns an error in the following cases:

- **WRONGTYPE**: The key exists but is not a stream
- **ERR no such key**: The stream does not exist
- **ERR syntax error**: Invalid command syntax or missing required arguments
- **ERR invalid duration**: Duration value is outside the valid range (1-86,400)
- **ERR invalid maxsize**: Maxsize value is outside the valid range (1-10,000)

XADD mystream * field value
XCFGSET mystream IDMP-DURATION 300
XCFGSET mystream IDMP-MAXSIZE 1000
XCFGSET mystream IDMP-DURATION 600 IDMP-MAXSIZE 500


XCLAIM

Summary: Changes, or acquires, ownership of a message in a consumer group, as if the message was delivered a consumer group member.

Overview

In the context of a stream consumer group, this command changes the ownership
of a pending message, so that the new owner is the consumer specified as the
command argument. Normally this is what happens:

1. There is a stream with an associated consumer group.
2. Some consumer A reads a message via [`XREADGROUP`]() from a stream, in the context of that consumer group.
3. As a side effect a pending message entry is created in the Pending Entries List (PEL) of the consumer group: it means the message was delivered to a given consumer, but it was not yet acknowledged via [`XACK`]().
4. Then suddenly that consumer fails forever.
5. Other consumers may inspect the list of pending messages, that are stale for quite some time, using the [`XPENDING`]() command. In order to continue processing such messages, they use `XCLAIM` to acquire the ownership of the message and continue. Consumers can also use the [`XAUTOCLAIM`]() command to automatically scan and claim stale pending messages.

This dynamic is clearly explained in the [Stream intro documentation]().

Note that the message is claimed only if its idle time is greater than the minimum idle time we specify when calling `XCLAIM`. Because as a side effect `XCLAIM` will also reset the idle time (since this is a new attempt at processing the message), two consumers trying to claim a message at the same time will never both succeed: only one will successfully claim the message. This avoids that we process a given message multiple times in a trivial way (yet multiple processing is possible and unavoidable in the general case).

Moreover, as a side effect, `XCLAIM` will increment the count of attempted deliveries of the message unless the `JUSTID` option has been specified (which only delivers the message ID, not the message itself). In this way messages that cannot be processed for some reason, for instance because the consumers crash attempting to process them, will start to have a larger counter and can be detected inside the system.

`XCLAIM` will not claim a message in the following cases:

1. The message doesn't exist in the group PEL (i.e. it was never read by any consumer)
2. The message exists in the group PEL but not in the stream itself (i.e. the message was read but never acknowledged, and then was deleted from the stream, either by trimming or by [`XDEL`]())

In both cases the reply will not contain a corresponding entry to that message (i.e. the length of the reply array may be smaller than the number of IDs provided to `XCLAIM`).
In the latter case, the message will also be deleted from the PEL in which it was found. This feature was introduced in Redis 7.0.

Command options

The command has multiple options, however most are mainly for internal use in
order to transfer the effects of `XCLAIM` or other commands to the AOF file
and to propagate the same effects to the replicas, and are unlikely to be
useful to normal users:

1. `IDLE <ms>`: Set the idle time (last time it was delivered) of the message. If IDLE is not specified, an IDLE of 0 is assumed, that is, the time count is reset because the message has now a new owner trying to process it.
2. `TIME <ms-unix-time>`: This is the same as IDLE but instead of a relative amount of milliseconds, it sets the idle time to a specific Unix time (in milliseconds). This is useful in order to rewrite the AOF file generating `XCLAIM` commands.
3. `RETRYCOUNT <count>`: Set the retry counter to the specified value. If not set, `XCLAIM` will increment the retry counter every time a message is delivered again.
4. `FORCE`: Creates the pending message entry in the PEL even if certain specified IDs are not already in the PEL assigned to a different client. However the message must be exist in the stream, otherwise the IDs of non existing messages are ignored.
5. `JUSTID`: Return just an array of IDs of messages successfully claimed, without returning the actual message. Using this option means the retry counter is not incremented.

Examples

[code example]

In the above example we claim the message with ID `1526569498055-0`, only if the message is idle for at least one hour without the original consumer or some other consumer making progresses (acknowledging or claiming it), and assigns the ownership to the consumer `Alice`.

Return information

**RESP2:**

Any of the following:
* [Array reply](../../develop/reference/protocol-spec#arrays): when the _JUSTID_ option is specified, an array of IDs of messages successfully claimed.
* [Array reply](../../develop/reference/protocol-spec#arrays): an array of stream entries, each of which contains an array of two elements, the entry ID and the entry data itself.

**RESP3:**

Any of the following:
* [Array reply](../../develop/reference/protocol-spec#arrays): when the _JUSTID_ option is specified, an array of IDs of messages successfully claimed.
* [Array reply](../../develop/reference/protocol-spec#arrays): an array of stream entries, each of which contains an array of two elements, the entry ID and the entry data itself.


XDEL

Summary: Returns the number of messages after removing them from a stream.

Overview

Removes the specified entries from a stream, and returns the number of entries
deleted.  This number may be less than the number of IDs passed to the command in
the case where some of the specified IDs do not exist in the stream.

Normally you may think at a Redis stream as an append-only data structure,
however Redis streams are represented in memory, so we are also able to
delete entries. This may be useful, for instance, in order to comply with
certain privacy policies.

Understanding the low level details of entries deletion

Redis streams are represented in a way that makes them memory efficient:
a radix tree is used in order to index macro-nodes that pack linearly tens
of stream entries. Normally what happens when you delete an entry from a stream
is that the entry is not *really* evicted, it just gets marked as deleted.

Eventually if all the entries in a macro-node are marked as deleted, the whole
node is destroyed and the memory reclaimed. This means that if you delete
a large amount of entries from a stream, for instance more than 50% of the
entries appended to the stream, the memory usage per entry may increment, since
what happens is that the stream will become fragmented. However the stream
performance will remain the same.

In future versions of Redis it is possible that we'll trigger a node garbage
collection in case a given macro-node reaches a given amount of deleted
entries. Currently with the usage we anticipate for this data structure, it is
not a good idea to add such complexity.

Examples

[code example]

| Redis
Software | Redis
Cloud | Notes |
|:----------------------|:-----------------|:------|
| ✅ Standard
<nobr>✅ Active-Active</nobr> | ✅ Standard
<nobr>✅ Active-Active</nobr> |  |

Return information

**RESP2:**

[Integer reply](../../develop/reference/protocol-spec#integers): the number of entries that were deleted.

**RESP3:**

[Integer reply](../../develop/reference/protocol-spec#integers): the number of entries that were deleted.


XDELEX

Summary: Deletes one or multiple entries from the stream.

Overview

Deletes one or multiple entries from the stream at the specified `key`.

`XDELEX` is an extension of the Redis Streams [`XDEL`]() command that provides more control over how message entries are deleted concerning consumer groups.

Required arguments

key

The name of the stream key.

IDS numids id [id ...]

The IDS block specifying which entries to delete:
- `numids`: The number of IDs that follow
- `id [id ...]`: One or more stream entry IDs to delete

Note: The IDS block can be at any position in the command, same as other commands.

Optional arguments

KEEPREF | DELREF | ACKED

Specifies how to handle consumer group references when deleting entries. Available since Redis 8.2. If no option is specified, `KEEPREF` is used by default:

- `KEEPREF` (default): Deletes the specified entries from the stream, but preserves existing references to these entries in all consumer groups' PEL (Pending Entries List). This behavior is similar to [`XDEL`]().
- `DELREF`: Deletes the specified entries from the stream and also removes all references to these entries from all consumer groups' pending entry lists, effectively cleaning up all traces of the messages. If an entry ID is not in the stream, but there are dangling references, `XDELEX` with `DELREF` would still remove all those references.
- `ACKED`: Only deletes entries that were read and acknowledged by all consumer groups.

The command provides fine-grained control over stream entry deletion, particularly useful when working with consumer groups where you need to manage pending entry references carefully.

Examples

XADD mystream * field1 value1
XADD mystream * field2 value2
XADD mystream * field3 value3
XRANGE mystream - +
XDELEX mystream KEEPREF IDS 2 1526919030474-55 1526919030474-56
XRANGE mystream - +

Return information

**RESP2:**

One of the following:

* [Array reply](): -1 for each requested ID when the given key does not exist.
* [Array reply](../../develop/reference/protocol-spec#arrays): For each ID:
    * [Integer reply](../../develop/reference/protocol-spec#integers): -1 if no such ID exists in the provided stream key.
    * [Integer reply](../../develop/reference/protocol-spec#integers): 1 if the entry was deleted from the stream.
    * [Integer reply](../../develop/reference/protocol-spec#integers): 2 if the entry was not deleted and there are still existing references (`ACKED` option); also, if there are no consumer groups.

**RESP3:**

One of the following:

* [Array reply](): -1 for each requested ID when the given key does not exist.
* [Array reply](../../develop/reference/protocol-spec#arrays): For each ID:
    * [Integer reply](../../develop/reference/protocol-spec#integers): -1 if no such ID exists in the provided stream key.
    * [Integer reply](../../develop/reference/protocol-spec#integers): 1 if the entry was deleted from the stream.
    * [Integer reply](../../develop/reference/protocol-spec#integers):  2 if the entry was not deleted and there are still existing references (`ACKED` option); also, if there are no consumer groups.


XGROUP

Summary: A container for consumer groups commands.

Content

This is a container command for stream consumer group management commands.

To see the list of available commands you can call [`XGROUP HELP`]().


XGROUP CREATE

Summary: Creates a consumer group.

Overview

Create a new consumer group uniquely identified by `<groupname>` for the stream stored at `<key>`

Every group has a unique name in a given stream.
When a consumer group with the same name already exists, the command returns a `-BUSYGROUP` error.

The command's `<id>` argument specifies the last delivered entry in the stream from the new group's perspective.
The special ID `$` is the ID of the last entry in the stream, but you can substitute it with any valid ID.

For example, if you want the group's consumers to fetch the entire stream from the beginning, use zero as the starting ID for the consumer group:

    XGROUP CREATE mystream mygroup 0

By default, the `XGROUP CREATE` command expects that the target stream exists, and returns an error when it doesn't.
If a stream does not exist, you can create it automatically with length of 0 by using the optional `MKSTREAM` subcommand as the last argument after the `<id>`:

    XGROUP CREATE mystream mygroup $ MKSTREAM

To enable consumer group lag tracking, specify the optional `entries_read` named argument with an arbitrary ID.
An arbitrary ID is any ID that isn't the ID of the stream's first entry, last entry, or zero ("0-0") ID.
Use it to find out how many entries are between the arbitrary ID (excluding it) and the stream's last entry.
Set the `entries_read` the stream's `entries_added` subtracted by the number of entries.

Return information

**RESP2:**

[Simple string reply](../../develop/reference/protocol-spec#simple-strings): `OK`.

**RESP3:**

[Simple string reply](../../develop/reference/protocol-spec#simple-strings): `OK`.


XGROUP CREATECONSUMER

Summary: Creates a consumer in a consumer group.

Overview

Create a consumer named `<consumername>` in the consumer group `<groupname>` of the stream that's stored at `<key>`.

Consumers are also created automatically whenever an operation, such as [`XREADGROUP`](), references a consumer that doesn't exist.
This is valid for [`XREADGROUP`]() only when there is data in the stream.

Return information

**RESP2:**

[Integer reply](../../develop/reference/protocol-spec#integers): the number of created consumers, either 0 or 1.

**RESP3:**

[Integer reply](../../develop/reference/protocol-spec#integers): the number of created consumers, either 0 or 1.


XGROUP DELCONSUMER

Summary: Deletes a consumer from a consumer group.

Overview

The `XGROUP DELCONSUMER` command deletes a consumer from the consumer group.

Sometimes it may be useful to remove old consumers since they are no longer used.

Note, however, that any pending messages that the consumer had will become unclaimable after it was deleted.
It is strongly recommended, therefore, that any pending messages are claimed or acknowledged prior to deleting the consumer from the group.

Return information

**RESP2:**

[Integer reply](../../develop/reference/protocol-spec#integers): the number of pending messages the consumer had before it was deleted.

**RESP3:**

[Integer reply](../../develop/reference/protocol-spec#integers): the number of pending messages the consumer had before it was deleted.


XGROUP DESTROY

Summary: Destroys a consumer group.

Overview

The `XGROUP DESTROY` command completely destroys a consumer group.

The consumer group will be destroyed even if there are active consumers, and pending messages, so make sure to call this command only when really needed.

Return information

**RESP2:**

[Integer reply](../../develop/reference/protocol-spec#integers): the number of destroyed consumer groups, either 0 or 1.

**RESP3:**

[Integer reply](../../develop/reference/protocol-spec#integers): the number of destroyed consumer groups, either 0 or 1.


XGROUP HELP

Summary: Returns helpful text about the different subcommands.

Overview

The `XGROUP HELP` command returns a helpful text describing the different subcommands.

| Redis
Software | Redis
Cloud | Notes |
|:----------------------|:-----------------|:------|
| ✅ Standard
<nobr>✅ Active-Active</nobr> | ✅ Standard
<nobr>✅ Active-Active</nobr> |  |

Return information

**RESP2:**

[Array reply](../../develop/reference/protocol-spec#arrays): a list of sub-commands and their descriptions.

**RESP3:**

[Array reply](../../develop/reference/protocol-spec#arrays): a list of sub-commands and their descriptions.


XGROUP SETID

Summary: Sets the last-delivered ID of a consumer group.

Overview

Set the **last delivered ID** for a consumer group.

Normally, a consumer group's last delivered ID is set when the group is created with [`XGROUP CREATE`]().
The `XGROUP SETID` command allows modifying the group's last delivered ID, without having to delete and recreate the group.
For instance if you want the consumers in a consumer group to re-process all the messages in a stream, you may want to set its next ID to 0:

    XGROUP SETID mystream mygroup 0

The optional `entries_read` argument can be specified to enable consumer group lag tracking for an arbitrary ID.
An arbitrary ID is any ID that isn't the ID of the stream's first entry, its last entry or the zero ("0-0") ID.
This can be useful you know exactly how many entries are between the arbitrary ID (excluding it) and the stream's last entry.
In such cases, the `entries_read` can be set to the stream's `entries_added` subtracted with the number of entries.

Return information


XINFO

Summary: A container for stream introspection commands.

Content

This is a container command for stream introspection commands.

To see the list of available commands you can call [`XINFO HELP`]().


XINFO CONSUMERS

Summary: Returns a list of the consumers in a consumer group.

Overview

This command returns the list of consumers that belong to the `<groupname>` consumer group of the stream stored at `<key>`.

The following information is provided for each consumer in the group:

* **name**: the consumer's name
* **pending**: the number of entries in the PEL: pending messages for the consumer, which are messages that were delivered but are yet to be acknowledged
* **idle**: the number of milliseconds that have passed since the consumer's last attempted interaction (Examples: [`XREADGROUP`](), [`XCLAIM`](), [`XAUTOCLAIM`]())
* **inactive**: the number of milliseconds that have passed since the consumer's last successful interaction (Examples: [`XREADGROUP`]() that actually read some entries into the PEL, [`XCLAIM`]()/[`XAUTOCLAIM`]() that actually claimed some entries)

Note that before Redis 7.2.0, **idle** used to denote the time passed since last successful interaction.
In 7.2.0, **inactive** was added and **idle** was changed to denote the time passed since last attempted interaction.

Examples

[code example]

Return information

**RESP2:**

[Array reply](../../develop/reference/protocol-spec#arrays): a list of consumers and their attributes.

**RESP3:**

[Array reply](../../develop/reference/protocol-spec#arrays): a list of consumers and their attributes.

> XINFO CONSUMERS mystream mygroup
1) 1) name
   2) "Alice"
   3) pending
   4) (integer) 1
   5) idle
   6) (integer) 9104628
   7) inactive
   8) (integer) 18104698
2) 1) name
   2) "Bob"
   3) pending
   4) (integer) 1
   5) idle
   6) (integer) 83841983
   7) inactive
   8) (integer) 993841998


XINFO GROUPS

Summary: Returns a list of the consumer groups of a stream.

Overview

This command returns the list of all consumer groups of the stream stored at `<key>`.

By default, only the following information is provided for each of the groups:

* **name**: the consumer group's name
* **consumers**: the number of consumers in the group
* **pending**: the length of the group's pending entries list (PEL), which are messages that were delivered but are yet to be acknowledged
* **last-delivered-id**: the ID of the last entry delivered to the group's consumers
* **entries-read**: the logical "read counter" of the last entry delivered to the group's consumers
* **lag**: the number of entries in the stream that are still waiting to be delivered to the group's consumers, or a NULL when that number can't be determined.

Consumer group lag

The lag of a given consumer group is the number of entries in the range between the group's `entries_read` and the stream's `entries_added`.
Put differently, it is the number of entries that are yet to be delivered to the group's consumers.

The values and trends of this metric are helpful in making scaling decisions about the consumer group.
You can address high lag values by adding more consumers to the group, whereas low values may indicate that you can remove consumers from the group to scale it down.

Redis reports the lag of a consumer group by keeping two counters: the number of all entries added to the stream and the number of logical reads made by the consumer group.
The lag is the difference between these two.

The stream's counter (the `entries_added` field of the [`XINFO STREAM`]() command) is incremented by one with every [`XADD`]() and counts all of the entries added to the stream during its lifetime.

The consumer group's counter, `entries_read`, is the logical counter of entries the group had read.
It is important to note that this counter is only a heuristic rather than an accurate counter, and therefore the use of the term "logical".
The counter attempts to reflect the number of entries that the group **should have read** to get to its current `last-delivered-id`.
The `entries_read` counter is accurate only in a perfect world, where a consumer group starts at the stream's first entry and processes all of its entries (i.e., no entries deleted before processing).

There are two special cases in which this mechanism is unable to report the lag:

1. A consumer group is created or set with an arbitrary last delivered ID (the [`XGROUP CREATE`]() and [`XGROUP SETID`]() commands, respectively).
    An arbitrary ID is any ID that isn't the ID of the stream's first entry, its last entry or the zero ("0-0") ID.
2. One or more entries between the group's `last-delivered-id` and the stream's `last-generated-id` were deleted (with [`XDEL`]() or a trimming operation).

In both cases, the group's read counter is considered invalid, and the returned value is set to NULL to signal that the lag isn't currently available.

However, the lag is only temporarily unavailable.
It is restored automatically during regular operation as consumers keep processing messages.
Once the consumer group delivers the last message in the stream to its members, it will be set with the correct logical read counter, and tracking its lag can be resumed.

Examples

[code example]

Return information

**RESP2:**

[Array reply](../../develop/reference/protocol-spec#arrays): a list of consumer groups.

**RESP3:**

[Array reply](../../develop/reference/protocol-spec#arrays): a list of consumer groups.

> XINFO GROUPS mystream
1)  1) "name"
    2) "mygroup"
    3) "consumers"
    4) (integer) 2
    5) "pending"
    6) (integer) 2
    7) "last-delivered-id"
    8) "1638126030001-0"
    9) "entries-read"
   10) (integer) 2
   11) "lag"
   12) (integer) 0
2)  1) "name"
    2) "some-other-group"
    3) "consumers"
    4) (integer) 1
    5) "pending"
    6) (integer) 0
    7) "last-delivered-id"
    8) "1638126028070-0"
    9) "entries-read"
   10) (integer) 1
   11) "lag"
   12) (integer) 1


XINFO HELP

Overview

The `XINFO HELP` command returns a helpful text describing the different subcommands.

Return information


XINFO STREAM

Summary: Returns information about a stream.

Overview

This command returns information about the stream stored at `<key>`.

The informative details provided by this command are:

* **length**: the number of entries in the stream (see [`XLEN`]())
* **radix-tree-keys**: the number of keys in the underlying radix data structure
* **radix-tree-nodes**: the number of nodes in the underlying radix data structure
* **groups**: the number of consumer groups defined for the stream
* **last-generated-id**: the ID of the least-recently entry that was added to the stream
* **max-deleted-entry-id**: the maximal entry ID that was deleted from the stream
* **entries-added**: the count of all entries added to the stream during its lifetime
* **first-entry**: the ID and field-value tuples of the first entry in the stream
* **last-entry**: the ID and field-value tuples of the last entry in the stream

IDMP (Idempotent Message Processing) fields

When IDMP is configured for the stream using [`XCFGSET`](), the following additional fields are included:

* **idmp-duration**: the duration in seconds that idempotent IDs are retained in the stream's IDMP map
* **idmp-maxsize**: the maximum number of idempotent IDs kept for each producer in the stream's IDMP map
* **pids-tracked**: the number of unique producer IDs currently being tracked
* **iids-tracked**: the total number of idempotent IDs currently stored across all producers
* **iids-added**: the total count of idempotent IDs that have been added to the stream during its lifetime
* **iids-duplicates**: the total count of duplicate messages that were detected and prevented by IDMP

The `FULL` modifier

The optional `FULL` modifier provides a more verbose reply.
When provided, the `FULL` reply includes an **entries** array that consists of the stream entries (ID and field-value tuples) in ascending order.
Furthermore, **groups** is also an array, and for each of the consumer groups it consists of the information reported by [`XINFO GROUPS`]() and [`XINFO CONSUMERS`]().

The following information is provided for each of the groups:

* **name**: the consumer group's name
* **last-delivered-id**: the ID of the last entry delivered to the group's consumers
* **entries-read**: the logical "read counter" of the last entry delivered to the group's consumers
* **lag**: the number of entries in the stream that are still waiting to be delivered to the group's consumers, or a NULL when that number can't be determined.
* **pel-count**: the length of the group's pending entries list (PEL), which are messages that were delivered but are yet to be acknowledged
* **pending**: an array with pending entries information (see below)
* **consumers**: an array with consumers information (see below)

The following information is provided for each pending entry:

1. The ID of the message.
2. The name of the consumer that fetched the message and has still to acknowledge it. We call it the current *owner* of the message.
3. The UNIX timestamp of when the message was delivered to this consumer.
4. The number of times this message was delivered.

The following information is provided for each consumer:

* **name**: the consumer's name
* **seen-time**: the UNIX timestamp of the last attempted interaction (Examples: [`XREADGROUP`](), [`XCLAIM`](), [`XAUTOCLAIM`]())
* **active-time**: the UNIX timestamp of the last successful interaction (Examples: [`XREADGROUP`]() that actually read some entries into the PEL, [`XCLAIM`]()/[`XAUTOCLAIM`]() that actually claimed some entries)
* **pel-count**: the number of entries in the PEL: pending messages for the consumer, which are messages that were delivered but are yet to be acknowledged
* **pending**: an array with pending entries information, has the same structure as described above, except the consumer name is omitted (redundant, since anyway we are in a specific consumer context)

Note that before Redis 7.2.0, **seen-time** used to denote the last successful interaction.
In 7.2.0, **active-time** was added and **seen-time** was changed to denote the last attempted interaction.

The `COUNT` option can be used to limit the number of stream and PEL entries that are returned (The first `<count>` entries are returned).
The default `COUNT` is 10 and a `COUNT` of 0 means that all entries will be returned (execution time may be long if the stream has a lot of entries).

Examples

Setting up a stream with IDMP:

[code example]

Default reply (with IDMP configured):

[code example]

Full reply:

[code example]

Return information

**RESP2:**

One of the following:
* [Array reply](../../develop/reference/protocol-spec#arrays): when the _FULL_ argument is used, a list of information about a stream in summary form.
* [Array reply](../../develop/reference/protocol-spec#arrays): when the _FULL_ argument is used, a list of information about a stream in extended form.

**RESP3:**

One of the following:
* [Map reply](../../develop/reference/protocol-spec#maps): when the _FULL_ argument was not given, a list of information about a stream in summary form.
* [Map reply](../../develop/reference/protocol-spec#maps): when the _FULL_ argument was given, a list of information about a stream in extended form.

> XADD mystream * message apple
"1638125133432-0"
> XADD mystream * message banana
"1638125141232-0"
> XCFGSET mystream DURATION 100 MAXSIZE 100
OK
> XADD mystream IDMP producer1 msg1 * field value
"1638125150000-0"

> XINFO STREAM mystream
 1) "length"
 2) (integer) 2
 3) "radix-tree-keys"
 4) (integer) 1
 5) "radix-tree-nodes"
 6) (integer) 2
 7) "last-generated-id"
 8) "1638125141232-0"
 9) "max-deleted-entry-id"
10) "0-0"
11) "entries-added"
12) (integer) 2
13) "recorded-first-entry-id"
14) "1719505260513-0"
15) "idmp-duration"
16) (integer) 100
17) "idmp-maxsize"
18) (integer) 100
19) "pids-tracked"
20) (integer) 1
21) "iids-tracked"
22) (integer) 1
23) "iids-added"
24) (integer) 1
25) "iids-duplicates"
26) (integer) 0
27) "groups"
28) (integer) 1
29) "first-entry"
30) 1) "1638125133432-0"
    2) 1) "message"
       2) "apple"
31) "last-entry"
32) 1) "1638125141232-0"
    2) 1) "message"
       2) "banana"


XLEN

Summary: Return the number of messages in a stream.

Overview

Returns the number of entries inside a stream. If the specified key does not
exist the command returns zero, as if the stream was empty.
However note that unlike other Redis types, zero-length streams are
possible, so you should call [`TYPE`]() or [`EXISTS`]() in order to check if
a key exists or not.

Streams are not auto-deleted once they have no entries inside (for instance
after an [`XDEL`]() call), because the stream may have consumer groups
associated with it.

Examples

XADD mystream * item 1
XADD mystream * item 2
XADD mystream * item 3
XLEN mystream

| Redis
Software | Redis
Cloud | Notes |
|:----------------------|:-----------------|:------|
| ✅ Standard
<nobr>✅ Active-Active</nobr> | ✅ Standard
<nobr>✅ Active-Active</nobr> |  |

Return information

**RESP2:**

[Integer reply](../../develop/reference/protocol-spec#integers): the number of entries of the stream at _key_.

**RESP3:**

[Integer reply](../../develop/reference/protocol-spec#integers): the number of entries of the stream at _key_.


XPENDING

Summary: Returns the information and entries from a stream consumer group's pending entries list.

Overview

Fetching data from a stream via a consumer group, and not acknowledging
such data, has the effect of creating *pending entries*. This is
well explained in the [`XREADGROUP`]() command, and even better in our
[introduction to Redis Streams](). The [`XACK`]() command
will immediately remove the pending entry from the Pending Entries List (PEL)
since once a message is successfully processed, there is no longer need
for the consumer group to track it and to remember the current owner
of the message.

The `XPENDING` command is the interface to inspect the list of pending
messages, and is as thus a very important command in order to observe
and understand what is happening with a streams consumer groups: what
clients are active, what messages are pending to be consumed, or to see
if there are idle messages. Moreover this command, together with [`XCLAIM`]()
is used in order to implement recovering of consumers that are failing
for a long time, and as a result certain messages are not processed: a
different consumer can claim the message and continue. This is better
explained in the [streams intro]() and in the
[`XCLAIM`]() command page, and is not covered here.

Summary form of XPENDING

When `XPENDING` is called with just a key name and a consumer group
name, it just outputs a summary about the pending messages in a given
consumer group. In the following example, we create a consumer group and
immediately create a pending message by reading from the group with
[`XREADGROUP`]().

[code example]

We expect the pending entries list for the consumer group `group55` to
have a message right now: consumer named `consumer-123` fetched the
message without acknowledging its processing. The simple `XPENDING`
form will give us this information:

[code example]

In this form, the command outputs the total number of pending messages for this
consumer group, which is one, followed by the smallest and greatest ID among the
pending messages, and then list every consumer in the consumer group with
at least one pending message, and the number of pending messages it has.

Extended form of XPENDING

The summary provides a good overview, but sometimes we are interested in the
details. In order to see all the pending messages with more associated
information we need to also pass a range of IDs, in a similar way we do it with
[`XRANGE`](), and a non optional *count* argument, to limit the number
of messages returned per call:

[code example]

In the extended form we no longer see the summary information, instead there
is detailed information for each message in the pending entries list. For
each message four attributes are returned:

1. The ID of the message.
2. The name of the consumer that fetched the message and has still to acknowledge it. We call it the current *owner* of the message.
3. The number of milliseconds that elapsed since the last time this message was delivered to this consumer.
4. The number of times this message was delivered.

The deliveries counter, that is the fourth element in the array, is incremented
when some other consumer *claims* the message with [`XCLAIM`](), or when the
message is delivered again via [`XREADGROUP`](), when accessing the history
of a consumer in a consumer group (see the [`XREADGROUP`]() page for more info).

It is possible to pass an additional argument to the command, in order
to see the messages having a specific owner:

[code example]

But in the above case the output would be the same, since we have pending
messages only for a single consumer. However what is important to keep in
mind is that this operation, filtering by a specific consumer, is not
inefficient even when there are many pending messages from many consumers:
we have a pending entries list data structure both globally, and for
every consumer, so we can very efficiently show just messages pending for
a single consumer.

Idle time filter

It is also possible to filter pending stream entries by their idle-time,
given in milliseconds (useful for [`XCLAIM`]()ing entries that have not been
processed for some time):

[code example]

The first case will return the first 10 (or less) PEL entries of the entire group
that are idle for over 9 seconds, whereas in the second case only those of
`consumer-123`.

Exclusive ranges and iterating the PEL

The `XPENDING` command allows iterating over the pending entries just like
[`XRANGE`]() and [`XREVRANGE`]() allow for the stream's entries. You can do this by
prefixing the ID of the last-read pending entry with the `(` character that
denotes an open (exclusive) range, and proving it to the subsequent call to the
command.

Return information

**RESP2:**

* [Array reply](../../develop/reference/protocol-spec#arrays): different data depending on the way XPENDING is called, as explained on this page.

**RESP3:**

* [Array reply](../../develop/reference/protocol-spec#arrays): different data depending on the way XPENDING is called, as explained on this page.

> XGROUP CREATE mystream group55 0-0
OK

> XREADGROUP GROUP group55 consumer-123 COUNT 1 STREAMS mystream >
1) 1) "mystream"
   2) 1) 1) 1526984818136-0
         2) 1) "duration"
            2) "1532"
            3) "event-id"
            4) "5"
            5) "user-id"
            6) "7782813"

> XPENDING mystream group55 - + 10 consumer-123

> XPENDING mystream group55 IDLE 9000 - + 10
> XPENDING mystream group55 IDLE 9000 - + 10 consumer-123


XRANGE

Summary: Returns the messages from a stream within a range of IDs.

Overview

The command returns the stream entries matching a given range of IDs.
The range is specified by a minimum and maximum ID. All the entries having
an ID between the two specified or exactly one of the two IDs specified
(closed interval) are returned.

The `XRANGE` command has a number of applications:

* Returning items in a specific time range. This is possible because
  Stream IDs are [related to time]().
* Iterating a stream incrementally, returning just
  a few items at every iteration. However it is semantically much more
  robust than the [`SCAN`]() family of functions.
* Fetching a single entry from a stream, providing the ID of the entry
  to fetch two times: as start and end of the query interval.

The command also has a reciprocal command returning items in the
reverse order, called [`XREVRANGE`](), which is otherwise identical.

`-` and `+` special IDs

The `-` and `+` special IDs mean respectively the minimum ID possible
and the maximum ID possible inside a stream, so the following command
will just return every entry in the stream:

[code example]

The `-` and `+` special IDs mean, respectively, the minimal and maximal range IDs,
however they are nicer to type.

Incomplete IDs

Stream IDs are composed of two parts, a Unix millisecond time stamp and a
sequence number for entries inserted in the same millisecond. It is possible
to use `XRANGE` specifying just the first part of the ID, the millisecond time,
like in the following example:

[code example]

In this case, `XRANGE` will auto-complete the start interval with `-0`
and end interval with `-18446744073709551615`, in order to return all the
entries that were generated between a given millisecond and the end of
the other specified millisecond. This also means that repeating the same
millisecond two times, we get all the entries within such millisecond,
because the sequence number range will be from zero to the maximum.

Used in this way `XRANGE` works as a range query command to obtain entries
in a specified time. This is very handy in order to access the history
of past events in a stream.

Exclusive ranges

The range is close (inclusive) by default, meaning that the reply can include
entries with IDs matching the query's start and end intervals. It is possible
to specify an open interval (exclusive) by prefixing the ID with the
character `(`. This is useful for iterating the stream, as explained below.

Returning a maximum number of entries

Using the **COUNT** option it is possible to reduce the number of entries
reported. This is a very important feature even if it may look marginal,
because it allows, for instance, to model operations such as *give me
the entry greater or equal to the following*:

[code example]

In the above case the entry `1526985054069-0` exists, otherwise the server
would have sent us the next one. Using `COUNT` is also the base in order to
use `XRANGE` as an iterator.

Iterating a stream

In order to iterate a stream, we can proceed as follows. Let's assume that
we want two elements per iteration. We start fetching the first two
elements, which is trivial:

[code example]

Then instead of starting the iteration again from `-`, as the start
of the range we use the entry ID of the *last* entry returned by the
previous `XRANGE` call as an exclusive interval.

The ID of the last entry is `1526985685298-0`, so we just prefix it
with a '(', and continue our iteration:

[code example]

And so forth. Eventually this will allow to visit all the entries in the
stream. Obviously, we can start the iteration from any ID, or even from
a specific time, by providing a given incomplete start ID. Moreover, we
can limit the iteration to a given ID or time, by providing an end
ID or incomplete ID instead of `+`.

The command [`XREAD`]() is also able to iterate the stream.
The command [`XREVRANGE`]() can iterate the stream reverse, from higher IDs
(or times) to lower IDs (or times).

Iterating with earlier versions of Redis

While exclusive range intervals are only available from Redis 6.2, it is still
possible to use a similar stream iteration pattern with earlier versions. You
start fetching from the stream the same way as described above to obtain the
first entries.

For the subsequent calls, you'll need to programmatically advance the last
entry's ID returned. Most Redis client should abstract this detail, but the
implementation can also be in the application if needed. In the example above,
this means incrementing the sequence of `1526985685298-0` by one, from 0 to 1.
The second call would, therefore, be:

[code example]

Also, note that once the sequence part of the last ID equals
18446744073709551615, you'll need to increment the timestamp and reset the
sequence part to 0. For example, incrementing the ID
`1526985685298-18446744073709551615` should result in `1526985685299-0`.

A symmetrical pattern applies to iterating the stream with [`XREVRANGE`](). The
only difference is that the client needs to decrement the ID for the subsequent
calls. When decrementing an ID with a sequence part of 0, the timestamp needs
to be decremented by 1 and the sequence set to 18446744073709551615.

Fetching single items

If you look for an `XGET` command you'll be disappointed because `XRANGE`
is effectively the way to go in order to fetch a single entry from a
stream. All you have to do is to specify the ID two times in the arguments
of XRANGE:

[code example]

Additional information about streams

For further information about Redis streams please check our
[introduction to Redis Streams document]().

Examples

XADD writers * name Virginia surname Woolf
XADD writers * name Jane surname Austen
XADD writers * name Toni surname Morrison
XADD writers * name Agatha surname Christie
XADD writers * name Ngozi surname Adichie
XLEN writers
XRANGE writers - + COUNT 2

| Redis
Software | Redis
Cloud | Notes |
|:----------------------|:-----------------|:------|
| ✅ Standard
<nobr>✅ Active-Active</nobr> | ✅ Standard
<nobr>✅ Active-Active</nobr> |  |

Return information

**RESP2:**

[Array reply](../../develop/reference/protocol-spec#arrays): a list of stream entries with IDs matching the specified range.

**RESP3:**

[Array reply](../../develop/reference/protocol-spec#arrays): a list of stream entries with IDs matching the specified range.


XREAD

Summary: Returns messages from multiple streams with IDs greater than the ones requested. Blocks until a message is available otherwise.

Overview

This command's behavior varies in clustered Redis environments. See the [multi-key operations]() page for more information.

Read data from one or multiple streams, only returning entries with an
ID greater than the last received ID reported by the caller.
This command has an option to block if items are not available, in a similar
fashion to [`BRPOP`]() or [`BZPOPMIN`]() and others.

Please note that before reading this page, if you are new to streams,
we recommend to read [our introduction to Redis Streams]().

Non-blocking usage

If the **BLOCK** option is not used, the command is synchronous, and can
be considered somewhat related to [`XRANGE`](): it will return a range of items
inside streams, however it has two fundamental differences compared to [`XRANGE`]()
even if we just consider the synchronous usage:

* This command can be called with multiple streams if we want to read at
  the same time from a number of keys. This is a key feature of `XREAD` because
  especially when blocking with **BLOCK**, to be able to listen with a single
  connection to multiple keys is a vital feature.
* While [`XRANGE`]() returns items in a range of IDs, `XREAD` is more suited in
  order to consume the stream starting from the first entry which is greater
  than any other entry we saw so far. So what we pass to `XREAD` is, for each
  stream, the ID of the last element that we received from that stream.

For example, if I have two streams `mystream` and `writers`, and I want to
read data from both the streams starting from the first element they contain,
I could call `XREAD` like in the following example.

Note: we use the **COUNT** option in the example, so that for each stream
the call will return at maximum two elements per stream.

[code example]

The **STREAMS** option is mandatory and MUST be the final option because
such option gets a variable length of argument in the following format:

    STREAMS key_1 key_2 key_3 ... key_N ID_1 ID_2 ID_3 ... ID_N

So we start with a list of keys, and later continue with all the associated
IDs, representing *the last ID we received for that stream*, so that the
call will serve us only greater IDs from the same stream.

For instance in the above example, the last items that we received
for the stream `mystream` has ID `1526999352406-0`, while for the
stream `writers` has the ID `1526985685298-0`.

To continue iterating the two streams I'll call:

[code example]

And so forth. Eventually, the call will not return any item, but just an
empty array, then we know that there is nothing more to fetch from our
stream (and we would have to retry the operation, hence this command
also supports a blocking mode).

Incomplete IDs

To use incomplete IDs is valid, like it is valid for [`XRANGE`](). However
here the sequence part of the ID, if missing, is always interpreted as
zero, so the command:

[code example]

is exactly equivalent to

[code example]

Blocking for data

In its synchronous form, the command can get new data as long as there
are more items available. However, at some point, we'll have to wait for
producers of data to use [`XADD`]() to push new entries inside the streams
we are consuming. In order to avoid polling at a fixed or adaptive interval
the command is able to block if it could not return any data, according
to the specified streams and IDs, and automatically unblock once one of
the requested keys accept data.

It is important to understand that this command *fans out* to all the
clients that are waiting for the same range of IDs, so every consumer will
get a copy of the data, unlike to what happens when blocking list pop
operations are used.

In order to block, the **BLOCK** option is used, together with the number
of milliseconds we want to block before timing out. Normally Redis blocking
commands take timeouts in seconds, however this command takes a millisecond
timeout, even if normally the server will have a timeout resolution near
to 0.1 seconds. This time it is possible to block for a shorter time in
certain use cases, and if the server internals will improve over time, it is
possible that the resolution of timeouts will improve.

When the **BLOCK** command is passed, but there is data to return at
least in one of the streams passed, the command is executed synchronously
*exactly like if the BLOCK option would be missing*.

This is an example of blocking invocation, where the command later returns
a null reply because the timeout has elapsed without new data arriving:

[code example]

The special `$` ID.

When blocking sometimes we want to receive just entries that are added
to the stream via [`XADD`]() starting from the moment we block. In such a case
we are not interested in the history of already added entries. For
this use case, we would have to check the stream top element ID, and use
such ID in the `XREAD` command line. This is not clean and requires to
call other commands, so instead it is possible to use the special `$`
ID to signal the stream that we want only the new things.

It is **very important** to understand that you should use the `$`
ID only for the first call to `XREAD`. Later the ID should be the one
of the last reported item in the stream, otherwise you could miss all
the entries that are added in between.

This is how a typical `XREAD` call looks like in the first iteration
of a consumer willing to consume only new entries:

[code example]

Once we get some replies, the next call will be something like:

[code example]

And so forth.

The special `+` ID

You can read the last entry in a single stream easily using the `XREVRANGE` command, like so:

[code example]
But this approach becomes slow as you add more streams because you must issue a separate command for each stream.
Instead, starting from Redis 7.4, you can use the `+` sign as a special ID.
This requests the last available entry in a stream. For example:

[code example]

Note that when using this special ID for a stream, the **COUNT** option will
be ignored (for the specific stream) since only the last entry can be returned.

How multiple clients blocked on a single stream are served

Blocking list operations on lists or sorted sets have a *pop* behavior.
Basically, the element is removed from the list or sorted set in order
to be returned to the client. In this scenario you want the items
to be consumed in a fair way, depending on the moment clients blocked
on a given key arrived. Normally Redis uses the FIFO semantics in this
use cases.

However note that with streams this is not a problem: stream entries
are not removed from the stream when clients are served, so every
client waiting will be served as soon as an [`XADD`]() command provides
data to the stream.

Reading the [Redis Streams introduction]() is highly
suggested in order to understand more about the streams overall behavior
and semantics.

Return information

**RESP2:**

One of the following:
* [Array reply](../../develop/reference/protocol-spec#arrays): an array where each element is an array composed of a two elements containing the key name and the entries reported for that key. The entries reported are full stream entries, having IDs and the list of all the fields and values. Field and values are guaranteed to be reported in the same order they were added by `XADD`.
* [Nil reply](../../develop/reference/protocol-spec#bulk-strings): if the _BLOCK_ option is given and a timeout occurs, or if there is no stream that can be served.

**RESP3:**

One of the following:
* [Map reply](../../develop/reference/protocol-spec#maps): A map of key-value elements where each element is composed of the key name and the entries reported for that key. The entries reported are full stream entries, having IDs and the list of all the fields and values. Field and values are guaranteed to be reported in the same order they were added by `XADD`.
* [Null reply](../../develop/reference/protocol-spec#nulls): if the _BLOCK_ option is given and a timeout occurs, or if there is no stream that can be served.

> XREAD COUNT 2 STREAMS mystream writers 0 0

> XREAD COUNT 2 STREAMS mystream writers 0-0 0-0

> XREAD BLOCK 1000 STREAMS mystream 1526999626221-0
(nil)

> XREAD BLOCK 5000 COUNT 100 STREAMS mystream $

> XREAD BLOCK 5000 COUNT 100 STREAMS mystream 1526999644174-3

> XREAD STREAMS streamA streamB streamC streamD + + + +