{
  "schema_version": 2,
  "id": "commands/xread",
  "title": "XREAD",
  "url": "https://redis.io/docs/latest/commands/xread/",
  "summary": "Returns messages from multiple streams with IDs greater than the ones requested. 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": "156210e4f503a9881a760897677247b2937ffce3bca849dc9350458f9d33f871",
  "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\nRead data from one or more streams, returning only entries with an\nID greater than the last received ID reported by the caller.\nThis command has an option to block if items are not available, in a similar\nfashion to [`BRPOP`](https://redis.io/docs/latest/commands/brpop) or [`BZPOPMIN`](https://redis.io/docs/latest/commands/bzpopmin) and others.\n\nIf you are new to streams, see [Introduction to Redis Streams](https://redis.io/docs/latest/develop/data-types/streams)."
    },
    {
      "id": "required-arguments",
      "title": "Required arguments",
      "role": "content",
      "text": "<details open><summary><code>STREAMS key [key ...] id [id ...]</code></summary>\n\nThe keys to read from, followed by an ID for each key. For each stream, entries with an ID greater than the given one are returned. Use `$` to read only entries added after the call.\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. 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. 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>"
    },
    {
      "id": "details",
      "title": "Details",
      "role": "content",
      "text": ""
    },
    {
      "id": "non-blocking-usage",
      "title": "Non-blocking usage",
      "role": "content",
      "text": "If the **BLOCK** option is not used, the command is synchronous, and can\nbe considered somewhat related to [`XRANGE`](https://redis.io/docs/latest/commands/xrange): it will return a range of items\ninside streams, however it has two fundamental differences compared to [`XRANGE`](https://redis.io/docs/latest/commands/xrange)\neven if we just consider the synchronous usage:\n\n* This command can be called with multiple streams if we want to read at\n  the same time from a number of keys. This is a key feature of `XREAD` because\n  especially when blocking with **BLOCK**, to be able to listen with a single\n  connection to multiple keys is a vital feature.\n* While [`XRANGE`](https://redis.io/docs/latest/commands/xrange) returns items in a range of IDs, `XREAD` is more suited in\n  order to consume the stream starting from the first entry which is greater\n  than any other entry we saw so far. So what we pass to `XREAD` is, for each\n  stream, the ID of the last element that we received from that stream.\n\nFor example, if I have two streams `mystream` and `writers`, and I want to\nread data from both the streams starting from the first element they contain,\nI could call `XREAD` like in the following example.\n\nNote: we use the `COUNT` option in the example, so that for each stream\nthe call will return at maximum two elements per stream.\n\n[code example]\n\nThe **STREAMS** option is mandatory and MUST be the final option because\nsuch option gets a variable length of argument in the following format:\n\n    STREAMS key_1 key_2 key_3 ... key_N ID_1 ID_2 ID_3 ... ID_N\n\nSo we start with a list of keys, and later continue with all the associated\nIDs, representing *the last ID we received for that stream*, so that the\ncall will serve us only greater IDs from the same stream.\n\nFor instance in the above example, the last items that we received\nfor the stream `mystream` has ID `1526999352406-0`, while for the\nstream `writers` has the ID `1526985685298-0`.\n\nTo continue iterating the two streams I'll call:\n\n[code example]\n\nAnd so forth. Eventually, the call will not return any item, but just an\nempty array, then we know that there is nothing more to fetch from our\nstream (and we would have to retry the operation, hence this command\nalso supports a blocking mode)."
    },
    {
      "id": "incomplete-ids",
      "title": "Incomplete IDs",
      "role": "content",
      "text": "To use incomplete IDs is valid, like it is valid for [`XRANGE`](https://redis.io/docs/latest/commands/xrange). However\nhere the sequence part of the ID, if missing, is always interpreted as\nzero, so the command:\n\n[code example]\n\nis exactly equivalent to\n\n[code example]"
    },
    {
      "id": "blocking-for-data",
      "title": "Blocking for data",
      "role": "content",
      "text": "In its synchronous form, the command can get new data as long as there\nare more items available. However, at some point, we'll have to wait for\nproducers of data to use [`XADD`](https://redis.io/docs/latest/commands/xadd) to push new entries inside the streams\nwe are consuming. In order to avoid polling at a fixed or adaptive interval\nthe command is able to block if it could not return any data, according\nto the specified streams and IDs, and automatically unblock once one of\nthe requested keys accept data.\n\nIt is important to understand that this command *fans out* to all the\nclients that are waiting for the same range of IDs, so every consumer will\nget a copy of the data, unlike to what happens when blocking list pop\noperations are used.\n\nIn order to block, the **BLOCK** option is used, together with the number\nof milliseconds we want to block before timing out. Normally Redis blocking\ncommands take timeouts in seconds, however this command takes a millisecond\ntimeout, even if normally the server will have a timeout resolution near\nto 0.1 seconds. This time it is possible to block for a shorter time in\ncertain use cases, and if the server internals will improve over time, it is\npossible that the resolution of timeouts will improve.\n\nWhen the **BLOCK** command is passed, but there is data to return at\nleast in one of the streams passed, the command is executed synchronously\n*exactly like if the BLOCK option would be missing*.\n\nThis is an example of blocking invocation, where the command later returns\na null reply because the timeout has elapsed without new data arriving:\n\n[code example]"
    },
    {
      "id": "the-special--id",
      "title": "The special `$` ID.",
      "role": "content",
      "text": "When blocking sometimes we want to receive just entries that are added\nto the stream via [`XADD`](https://redis.io/docs/latest/commands/xadd) starting from the moment we block. In such a case\nwe are not interested in the history of already added entries. For\nthis use case, we would have to check the stream top element ID, and use\nsuch ID in the `XREAD` command line. This is not clean and requires to\ncall other commands, so instead it is possible to use the special `$`\nID to signal the stream that we want only the new things.\n\nIt is **very important** to understand that you should use the `$`\nID only for the first call to `XREAD`. Later the ID should be the one\nof the last reported item in the stream, otherwise you could miss all\nthe entries that are added in between.\n\nThis is how a typical `XREAD` call looks like in the first iteration\nof a consumer willing to consume only new entries:\n\n[code example]\n\nOnce we get some replies, the next call will be something like:\n\n[code example]\n\nAnd so forth."
    },
    {
      "id": "the-special--id-1",
      "title": "The special `+` ID",
      "role": "content",
      "text": "You can read the last entry in a single stream easily using the `XREVRANGE` command, like so:\n\n[code example]\nBut this approach becomes slow as you add more streams because you must issue a separate command for each stream.\nInstead, starting from Redis 7.4, you can use the `+` sign as a special ID.\nThis requests the last available entry in a stream. For example:\n\n[code example]\n\nNote that when using this special ID for a stream, the `COUNT` option will\nbe ignored (for the specific stream) since only the last entry can be returned."
    },
    {
      "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 `XREAD` accepts multiple streams in one call, a read over N streams with\n`COUNT C` can return up to `N * C` entries, and the total reply size is otherwise\nunbounded. `MAXCOUNT` and `MAXSIZE` give clients a reliable way to bound the\nwork 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\nGiven three streams, each with 100 entries:\n\n[code example]\n\nA single oversized entry is always returned, even when it exceeds `MAXSIZE`:\n\n[code example]"
    },
    {
      "id": "how-multiple-clients-blocked-on-a-single-stream-are-served",
      "title": "How multiple clients blocked on a single stream are served",
      "role": "content",
      "text": "Blocking list operations on lists or sorted sets have a *pop* behavior.\nBasically, the element is removed from the list or sorted set in order\nto be returned to the client. In this scenario you want the items\nto be consumed in a fair way, depending on the moment clients blocked\non a given key arrived. Normally Redis uses the FIFO semantics in this\nuse cases.\n\nHowever note that with streams this is not a problem: stream entries\nare not removed from the stream when clients are served, so every\nclient waiting will be served as soon as an [`XADD`](https://redis.io/docs/latest/commands/xadd) command provides\ndata to the stream.\n\nReading the [Redis Streams introduction](https://redis.io/docs/latest/develop/data-types/streams) is highly\nsuggested in order to understand more about the streams overall behavior\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": "non-blocking-usage-ex0",
      "language": "plaintext",
      "code": "> XREAD COUNT 2 STREAMS mystream writers 0-0 0-0\n1) 1) \"mystream\"\n   2) 1) 1) 1526984818136-0\n         2) 1) \"duration\"\n            2) \"1532\"\n            3) \"event-id\"\n            4) \"5\"\n            5) \"user-id\"\n            6) \"7782813\"\n      2) 1) 1526999352406-0\n         2) 1) \"duration\"\n            2) \"812\"\n            3) \"event-id\"\n            4) \"9\"\n            5) \"user-id\"\n            6) \"388234\"\n2) 1) \"writers\"\n   2) 1) 1) 1526985676425-0\n         2) 1) \"name\"\n            2) \"Virginia\"\n            3) \"surname\"\n            4) \"Woolf\"\n      2) 1) 1526985685298-0\n         2) 1) \"name\"\n            2) \"Jane\"\n            3) \"surname\"\n            4) \"Austen\"",
      "section_id": "non-blocking-usage"
    },
    {
      "id": "non-blocking-usage-ex1",
      "language": "plaintext",
      "code": "> XREAD COUNT 2 STREAMS mystream writers 1526999352406-0 1526985685298-0\n1) 1) \"mystream\"\n   2) 1) 1) 1526999626221-0\n         2) 1) \"duration\"\n            2) \"911\"\n            3) \"event-id\"\n            4) \"7\"\n            5) \"user-id\"\n            6) \"9488232\"\n2) 1) \"writers\"\n   2) 1) 1) 1526985691746-0\n         2) 1) \"name\"\n            2) \"Toni\"\n            3) \"surname\"\n            4) \"Morrison\"\n      2) 1) 1526985712947-0\n         2) 1) \"name\"\n            2) \"Agatha\"\n            3) \"surname\"\n            4) \"Christie\"",
      "section_id": "non-blocking-usage"
    },
    {
      "id": "incomplete-ids-ex0",
      "language": "plaintext",
      "code": "> XREAD COUNT 2 STREAMS mystream writers 0 0",
      "section_id": "incomplete-ids"
    },
    {
      "id": "incomplete-ids-ex1",
      "language": "plaintext",
      "code": "> XREAD COUNT 2 STREAMS mystream writers 0-0 0-0",
      "section_id": "incomplete-ids"
    },
    {
      "id": "blocking-for-data-ex0",
      "language": "plaintext",
      "code": "> XREAD BLOCK 1000 STREAMS mystream 1526999626221-0\n(nil)",
      "section_id": "blocking-for-data"
    },
    {
      "id": "the-special--id-ex0",
      "language": "plaintext",
      "code": "> XREAD BLOCK 5000 COUNT 100 STREAMS mystream $",
      "section_id": "the-special--id"
    },
    {
      "id": "the-special--id-ex1",
      "language": "plaintext",
      "code": "> XREAD BLOCK 5000 COUNT 100 STREAMS mystream 1526999644174-3",
      "section_id": "the-special--id"
    },
    {
      "id": "the-special--id-1-ex0",
      "language": "plaintext",
      "code": "> XREVRANGE stream + - COUNT 1",
      "section_id": "the-special--id-1"
    },
    {
      "id": "the-special--id-1-ex1",
      "language": "plaintext",
      "code": "> XREAD STREAMS streamA streamB streamC streamD + + + +",
      "section_id": "the-special--id-1"
    },
    {
      "id": "the-maxcount-and-maxsize-options-ex0",
      "language": "plaintext",
      "code": "> XREAD COUNT 50 STREAMS s1 s2 s3 0 0 0\n# returns 150 entries total (50 per stream)\n\n> XREAD COUNT 50 MAXCOUNT 80 STREAMS s1 s2 s3 0 0 0\n# returns 80 entries total — capped across all streams\n\n> XREAD MAXCOUNT 7 STREAMS s1 s2 s3 0 0 0\n# returns 7 entries total, all from s1\n\n> XREAD MAXCOUNT 5 MAXSIZE 100000 STREAMS s1 s2 s3 0 0 0\n# returns 5 entries (MAXCOUNT is the tighter bound)",
      "section_id": "the-maxcount-and-maxsize-options"
    },
    {
      "id": "the-maxcount-and-maxsize-options-ex1",
      "language": "plaintext",
      "code": "> XADD bigstream 1-1 f <5000-byte value>\n> XREAD MAXSIZE 50 STREAMS bigstream 0\n1) 1) \"bigstream\"\n   2) 1) 1) \"1-1\"\n         2) 1) \"f\"\n            2) \"<5000-byte value>\"\n# the single entry exceeds MAXSIZE but is still returned",
      "section_id": "the-maxcount-and-maxsize-options"
    }
  ]
}
