{
  "schema_version": 2,
  "id": "commands/xreadgroup",
  "title": "XREADGROUP",
  "url": "https://redis.io/docs/latest/commands/xreadgroup/",
  "summary": "Returns new or historical messages from a stream for a consumer in a group. Blocks until a message is available otherwise.",
  "since": "5.0.0",
  "tags": [
    "docs",
    "develop",
    "stack",
    "oss",
    "rs",
    "rc",
    "oss",
    "kubernetes",
    "clients"
  ],
  "last_updated": "2026-09-18T16:45:25+01:00",
  "page_type": "content",
  "content_hash": "1733213a2c78687a32bd4777a8d2b42399ca8dd78612e384e02e16f319ee1408",
  "sections": [
    {
      "id": "overview",
      "title": "Overview",
      "role": "overview",
      "text": "> [!NOTE]\n> This command's behavior varies in clustered Redis environments. See the [multi-key operations](https://redis.io/docs/latest/develop/using-commands/multi-key-operations) page for more information.\n\n\nThe `XREADGROUP` command is a special version of the [`XREAD`](https://redis.io/docs/latest/commands/xread) command\nwith support for consumer groups. See \n[`XREAD`](https://redis.io/docs/latest/commands/xread) command before reading this page.\n\nIf you are new to streams, see \n[Introduction to Redis Streams](https://redis.io/docs/latest/develop/data-types/streams).\nMake sure you understand consumer groups in the introduction."
    },
    {
      "id": "required-arguments",
      "title": "Required arguments",
      "role": "content",
      "text": "<details open><summary><code>GROUP group consumer</code></summary>\n\nThe consumer group and the consumer name to read as.\n\n</details>\n\n<details open><summary><code>STREAMS key [key ...] id [id ...]</code></summary>\n\nThe keys to read from, followed by an ID for each key. Use `>` to read messages never delivered to any other consumer, or a specific ID to read from this consumer's pending list.\n\n</details>"
    },
    {
      "id": "optional-arguments",
      "title": "Optional arguments",
      "role": "parameters",
      "text": "<details open><summary><code>COUNT count</code></summary>\n\nThe maximum number of entries to return per stream.\n\n</details>\n\n<details open><summary><code>MAXCOUNT maxcount</code></summary>\n\nAdded in Redis 8.10. The maximum number of entries to return in total across all streams named in the command. Unlike `COUNT`, which limits entries on a per-stream basis, `MAXCOUNT` applies a single cumulative budget for the whole command. It must be a positive integer, and it must be greater than or equal to `COUNT` when both are given. When `COUNT` is omitted, `MAXCOUNT` alone bounds the total. It works for both new (`>`) reads and history/pending entries list (PEL) reads. See [The MAXCOUNT and MAXSIZE options](#the-maxcount-and-maxsize-options) for details.\n\n</details>\n\n<details open><summary><code>MAXSIZE maxsize</code></summary>\n\nAdded in Redis 8.10. The maximum size, in bytes, of the reply across all streams named in the command. It must be a positive integer. At least one entry is always returned, so a single entry larger than `MAXSIZE` is still returned rather than yielding an empty reply. For `MAXSIZE`, the limit is checked before delivering the next new or PEL entry, so a skipped entry is neither sent nor added to the consumer's PEL. See [The MAXCOUNT and MAXSIZE options](#the-maxcount-and-maxsize-options) for details.\n\n</details>\n\n<details open><summary><code>BLOCK milliseconds</code></summary>\n\nBlock for up to this many milliseconds if no entries are available. `0` blocks indefinitely.\n\n</details>\n\n<details open><summary><code>CLAIM min-idle-time</code></summary>\n\nClaim messages that have been idle for at least this long (in milliseconds) before reading.\n\n</details>\n\n<details open><summary><code>NOACK</code></summary>\n\nDo not add the read messages to the PEL; treat them as acknowledged immediately.\n\n</details>"
    },
    {
      "id": "details",
      "title": "Details",
      "role": "content",
      "text": ""
    },
    {
      "id": "consumer-groups-in-30-seconds",
      "title": "Consumer groups in 30 seconds",
      "role": "content",
      "text": "The difference between this command and the vanilla [`XREAD`](https://redis.io/docs/latest/commands/xread) is that this\none supports consumer groups.\n\nWithout consumer groups, just using [`XREAD`](https://redis.io/docs/latest/commands/xread), all the clients are served with all the entries arriving in a stream. Instead using consumer groups with `XREADGROUP`, it is possible to create groups of clients that consume different parts of the messages arriving in a given stream. If, for instance, the stream gets the new entries A, B, and C and there are two consumers reading via a consumer group, one client will get, for instance, the messages A and C, and the other the message B, and so forth.\n\nWithin a consumer group, a given consumer (that is, just a client consuming messages from the stream), has to identify with a unique *consumer name*. Which is just a string.\n\nOne of the guarantees of consumer groups is that a given consumer can only see the history of messages that were delivered to it, so a message has just a single owner. However there is a special feature called *message claiming* that allows other consumers to claim messages in case there is a non recoverable failure of some consumer. In order to implement such semantics, consumer groups require explicit acknowledgment of the messages successfully processed by the consumer, via the [`XACK`](https://redis.io/docs/latest/commands/xack) command. This is needed because the stream will track, for each consumer group, who is processing what message.\n\nThis is how to understand if you want to use a consumer group or not:\n\n1. If you have a stream and multiple clients, and you want all the clients to get all the messages, you do not need a consumer group.\n2. If you have a stream and multiple clients, and you want the stream to be *partitioned* or *sharded* across your clients, so that each client will get a sub set of the messages arriving in a stream, you need a consumer group."
    },
    {
      "id": "differences-between-xread-and-xreadgroup",
      "title": "Differences between XREAD and XREADGROUP",
      "role": "content",
      "text": "From the point of view of the syntax, the commands are almost the same,\nhowever `XREADGROUP` *requires* a special and mandatory option:\n\n    GROUP `group-name` `consumer-name`\n\nThe group name is just the name of a consumer group associated to the stream.\nThe group is created using the [`XGROUP`](https://redis.io/docs/latest/commands/xgroup) command. The consumer name is the\nstring that is used by the client to identify itself inside the group.\nThe consumer is auto created inside the consumer group the first time it\nis seen. Different clients should select a different consumer name.\n\nWhen you read with `XREADGROUP`, the server will *remember* that a given\nmessage was delivered to you: the message will be stored inside the\nconsumer group in what is called a Pending Entries List (PEL), that is\na list of message IDs delivered but not yet acknowledged.\n\nThe client will have to acknowledge the message processing using [`XACK`](https://redis.io/docs/latest/commands/xack)\nin order for the pending entry to be removed from the PEL. The PEL\ncan be inspected using the [`XPENDING`](https://redis.io/docs/latest/commands/xpending) command.\n\nThe `NOACK` subcommand can be used to avoid adding the message to the PEL in\ncases where reliability is not a requirement and the occasional message loss\nis acceptable. This is equivalent to acknowledging the message when it is read.\nWhen used together with `CLAIM`, `NOACK` does not apply for retrieved pending entries.\n\nThe ID to specify in the **STREAMS** option when using `XREADGROUP` can\nbe one of the following two:\n\n* When the special `>` id is specified without `CLAIM`, the consumer wants to receive only messages that were never delivered to any other consumer, hence, just new messages.\n* When the special `>` id is specified with `CLAIM`, the consumer wants, in addition, to receive messages that have been delivered to some consumer and have been pending for at least min-idle-time milliseconds.\n* Any other ID, that is, 0 or any other valid ID or incomplete ID (just the millisecond time part), will have the effect of returning entries that are pending for the consumer sending the command with IDs greater than the one provided. So basically if the ID is not `>`, then the command will just let the client access its pending entries: messages delivered to it, but not yet acknowledged. Note that in this case, `BLOCK`, `NOACK`, and `CLAIM` are ignored.\n\nLike [`XREAD`](https://redis.io/docs/latest/commands/xread) the `XREADGROUP` command can be used in a blocking way. There\nare no differences in this regard."
    },
    {
      "id": "the-claim-option",
      "title": "The CLAIM option",
      "role": "content",
      "text": "The `CLAIM` option was added as part of Redis 8.4. When `CLAIM min-idle-time` is specified, Redis will first try to claim messages which have been pending for at least min-idle-time milliseconds from the consumer group of each specified stream key. The pending messages with the highest idle time would be claimed first. Note that the `CLAIM min-idle-time` condition may become true for some pending entries during the `BLOCK milliseconds` period (if specified).\n\nIf there are no such messages, Redis will continue as normal (consume incoming messages).\n\n`CLAIM min-idle-time` is ignored if the specified id is not `>`.\n\nMessages that have been released back to the group using [`XNACK`](https://redis.io/docs/latest/commands/xnack), added in Redis 8.8, are immediately claimable since their delivery time is set to 0, satisfying any minimum idle time requirement.\n\n#### Reply extension for claimed entries\n\nWhen `CLAIM min-idle-time` is used, additional information is provided for each pending entry retrieved (similar to the reply of [`XPENDING`](https://redis.io/docs/latest/commands/xpending)). For each claimed pending entry, the reply includes:\n\n1. Entry id\n2. Field-value pairs\n3. Milliseconds since the last time this entry was delivered\n4. Number of times this entry was previously delivered\n\n#### Ordering guarantees\n\nWithin each stream, entries are reported in the same order they were added by `XADD` (older first).\n\nWhen not blocked, across streams, entries are reported in the order the streams were specified.\n\nWhen using `CLAIM`, the following ordering guarantees apply per stream:\n\n- Idle pending entries are reported first, then incoming entries\n- Among pending entries, messages released via [`XNACK`](https://redis.io/docs/latest/commands/xnack) are prioritized and reported first\n- Other idle pending entries are ordered by idle time (longer first)\n- Incoming entries are reported in the order they were added by `XADD` (older first)\n\nFor example, if there are 20 idle pending entries and 200 incoming entries (in all the specified streams together):\n- When calling `XREADGROUP ... CLAIM ...`, you would retrieve 220 entries in the reply\n- When calling `XREADGROUP ... COUNT 100 ... CLAIM ...`, you would retrieve the 20 idle pending entries + 80 incoming entries in the reply"
    },
    {
      "id": "the-maxcount-and-maxsize-options",
      "title": "The MAXCOUNT and MAXSIZE options",
      "role": "content",
      "text": "Added in Redis 8.10, the `MAXCOUNT` and `MAXSIZE` options cap the *cumulative*\nreply across all streams named in a single command. This is different from\n`COUNT`, which limits the number of entries returned on a *per-stream* basis.\nBecause `XREADGROUP` accepts multiple streams in one call, a read over N streams\nwith `COUNT C` can return up to `N * C` entries, and the total reply size is\notherwise unbounded. `MAXCOUNT` and `MAXSIZE` give clients a reliable way to\nbound the work and memory of a single multi-stream read.\n\n* `MAXCOUNT` caps the total number of entries returned across all streams. It\n  must be a positive integer, and it must be greater than or equal to `COUNT`\n  when both are given (since it is a cumulative cap over a per-stream limit).\n  When `COUNT` is omitted, `MAXCOUNT` alone bounds the total, filling from\n  the first stream onward.\n* `MAXSIZE` caps the total reply size in bytes. It must be a positive integer.\n* At least one entry is always returned. The byte budget is never enforced\n  until at least one entry has been emitted across the whole reply, so a single\n  entry larger than `MAXSIZE` is still returned rather than yielding an empty\n  reply.\n* When both options are given, whichever bound is reached first wins.\n\nBoth caps apply to new (`>`) reads and to history/PEL reads, and they are honored\nafter a blocking client is unblocked. For `MAXSIZE`, the limit is checked\n*before* delivering the next new or PEL entry, so a skipped entry is neither sent\nnor added to the consumer's PEL.\n\nGiven three streams, each with 100 entries that the consumer reads as new\nmessages:\n\n[code example]"
    },
    {
      "id": "what-happens-when-a-message-is-delivered-to-a-consumer",
      "title": "What happens when a message is delivered to a consumer?",
      "role": "content",
      "text": "Two things:\n\n1. If the message was never delivered to anyone, that is, if we are talking about a new message, then a PEL (Pending Entries List) is created.\n2. If instead the message was already delivered to this consumer, and it is just re-fetching the same message again, then the *last delivery counter* is updated to the current time, and the *number of deliveries* is incremented by one. You can access those message properties using the [`XPENDING`](https://redis.io/docs/latest/commands/xpending) command."
    },
    {
      "id": "usage-example",
      "title": "Usage example",
      "role": "syntax",
      "text": "Normally you use the command like that in order to get new messages and\nprocess them. In pseudo-code:\n\n[code example]\n\nIn this way the example consumer code will fetch only new messages, process\nthem, and acknowledge them via [`XACK`](https://redis.io/docs/latest/commands/xack). However the example code above is\nnot complete, because it does not handle recovering after a crash. What\nwill happen if we crash in the middle of processing messages, is that our\nmessages will remain in the pending entries list, so we can access our\nhistory by giving `XREADGROUP` initially an ID of 0, and performing the same\nloop. Once providing an ID of 0 the reply is an empty set of messages, we\nknow that we processed and acknowledged all the pending messages: we\ncan start to use `>` as ID, in order to get the new messages and rejoin the\nconsumers that are processing new things.\n\nTo see how the command actually replies, please check the [`XREAD`](https://redis.io/docs/latest/commands/xread) command page."
    },
    {
      "id": "what-happens-when-a-pending-message-is-deleted",
      "title": "What happens when a pending message is deleted?",
      "role": "content",
      "text": "Entries may be deleted from the stream due to trimming with [`XADD`](https://redis.io/docs/latest/commands/xadd) or [`XTRIM`](https://redis.io/docs/latest/commands/xtrim),\nor explicit calls to [`XDEL`](https://redis.io/docs/latest/commands/xdel), [`XDELEX`](https://redis.io/docs/latest/commands/xdelex), or [`XACKDEL`](https://redis.io/docs/latest/commands/xackdel).\n\nWhen an entry is trimmed with [`XADD`](https://redis.io/docs/latest/commands/xadd) or [`XTRIM`](https://redis.io/docs/latest/commands/xtrim) and `DELREF` or `ACKED` are not specified,\ndeleted with [`XDEL`](https://redis.io/docs/latest/commands/xdel), or deleted with [`XDELEX`](https://redis.io/docs/latest/commands/xdelex) or [`XACKDEL`](https://redis.io/docs/latest/commands/xackdel) \nand `DELREF` or `ACKED` are not specified, Redis doesn't prevent the deletion of entries that are present in the stream's PELs.\nWhen this happens, the PELs retain the deleted entries' IDs, but the actual entry payload is no longer available.\nTherefore, when reading such PEL entries, Redis will return a null value in place of their respective data.\n\nExample:\n\n[code example]\n\nSee [Introduction to Redis streams](https://redis.io/docs/latest/develop/data-types/streams) to understand more about the overall behavior of Redis streams.\nand semantics."
    },
    {
      "id": "redis-software-and-redis-cloud-compatibility",
      "title": "Redis Software and Redis Cloud compatibility",
      "role": "content",
      "text": "| Redis<br />Software | Redis<br />Cloud | <span style=\"min-width: 9em; display: table-cell\">Notes</span> |\n|:----------------------|:-----------------|:------|\n| <span title=\"Supported\">&#x2705; Standard</span><br /><span title=\"Supported\"><nobr>&#x2705; Active-Active</nobr></span> | <span title=\"Supported\">&#x2705; Standard</span><br /><span title=\"Supported\"><nobr>&#x2705; Active-Active</nobr></span> |  |"
    },
    {
      "id": "return-information",
      "title": "Return information",
      "role": "returns",
      "text": "**RESP2:**\n\nOne of the following:\n* [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`.\n* [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.\n\n**RESP3:**\n\nOne of the following:\n* [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`.\n* [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."
    }
  ],
  "examples": [
    {
      "id": "the-maxcount-and-maxsize-options-ex0",
      "language": "plaintext",
      "code": "> XREADGROUP GROUP g c COUNT 50 STREAMS s1 s2 s3 > > >\n# returns 150 entries total (50 per stream)\n\n> XREADGROUP GROUP g c COUNT 50 MAXCOUNT 80 STREAMS s1 s2 s3 > > >\n# returns 80 entries total — capped across all streams\n\n> XREADGROUP GROUP g c MAXCOUNT 5 MAXSIZE 100000 STREAMS s1 s2 s3 > > >\n# returns 5 entries (MAXCOUNT is the tighter bound)",
      "section_id": "the-maxcount-and-maxsize-options"
    },
    {
      "id": "usage-example-ex0",
      "language": "plaintext",
      "code": "WHILE true\n    entries = XREADGROUP GROUP $GroupName $ConsumerName BLOCK 2000 COUNT 10 STREAMS mystream >\n    if entries == nil\n        puts \"Timeout... try again\"\n        CONTINUE\n    end\n\n    FOREACH entries AS stream_entries\n        FOREACH stream_entries as message\n            process_message(message.id,message.fields)\n\n            # ACK the message as processed\n            XACK mystream $GroupName message.id\n        END\n    END\nEND",
      "section_id": "usage-example"
    },
    {
      "id": "what-happens-when-a-pending-message-is-deleted-ex0",
      "language": "plaintext",
      "code": "> XADD mystream 1 myfield mydata\n\"1-0\"\n> XGROUP CREATE mystream mygroup 0\nOK\n> XREADGROUP GROUP mygroup myconsumer STREAMS mystream >\n1) 1) \"mystream\"\n   2) 1) 1) \"1-0\"\n         2) 1) \"myfield\"\n            2) \"mydata\"\n> XDEL mystream 1-0\n(integer) 1\n> XREADGROUP GROUP mygroup myconsumer STREAMS mystream 0\n1) 1) \"mystream\"\n   2) 1) 1) \"1-0\"\n         2) (nil)",
      "section_id": "what-happens-when-a-pending-message-is-deleted"
    }
  ]
}
