{
  "schema_version": 2,
  "id": "develop/clients/lettuce/transpipe",
  "title": "Pipelines and transactions",
  "url": "https://redis.io/docs/latest/develop/clients/lettuce/transpipe/",
  "summary": "Learn how to use Redis pipelines and transactions",
  "tags": [
    "docs",
    "develop",
    "stack",
    "oss",
    "rs",
    "rc",
    "oss",
    "kubernetes",
    "clients"
  ],
  "last_updated": "2026-09-09T09:47:45+01:00",
  "page_type": "content",
  "content_hash": "7839a2fb06cafe381bf3501e84163c3776e51121da9579c906e009efb33a5dd5",
  "sections": [
    {
      "id": "execute-a-pipeline",
      "title": "Execute a pipeline",
      "role": "content",
      "text": "With Lettuce, you pipeline commands by buffering them on the client and then\nflushing them to the server together. The synchronous and asynchronous APIs do\nthis by temporarily disabling auto-flushing with `setAutoFlushCommands()` and\nthen calling `flushCommands()`; each buffered command returns a `RedisFuture`\nthat you can inspect after the batch is flushed. The reactive API pipelines\ncommands by composing the corresponding `Mono` and `Flux` publishers:\n\nFoundational: Batch multiple Lettuce commands and flush them to the server together\n\n**Difficulty:** Beginner\n\n**Available in:** Java, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce)\n\n##### Java\n\n[code example]\n\n##### Java (Asynchronous - Lettuce)\n\n[code example]\n\n##### Java (Reactive - Lettuce)\n\n[code example]"
    },
    {
      "id": "execute-a-transaction",
      "title": "Execute a transaction",
      "role": "content",
      "text": "Lettuce transactions use the Redis `MULTI` and `EXEC` commands. Commands issued\nbetween `multi()` and `exec()` are queued on the server and only run when\n`exec()` completes. The return value from `exec()` is a `TransactionResult`\nthat contains the results in order:\n\nFoundational: Use MULTI and EXEC with Lettuce to execute multiple commands atomically\n\n**Difficulty:** Beginner\n\n**Available in:** Java, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce)\n\n##### Java\n\n[code example]\n\n##### Java (Asynchronous - Lettuce)\n\n[code example]\n\n##### Java (Reactive - Lettuce)\n\n[code example]"
    },
    {
      "id": "watch-keys-for-changes",
      "title": "Watch keys for changes",
      "role": "content",
      "text": "Redis supports *optimistic locking* to avoid inconsistent updates to keys that\nseveral clients may modify at the same time. The basic idea is to watch for\nchanges to any keys that you use in a transaction while you are preparing the\nupdate. If the watched keys do change, Redis discards the transaction and you\nmust retry using the latest value from Redis. See\n[Transactions](https://redis.io/docs/latest/develop/using-commands/transactions)\nfor more information about optimistic locking.\n\nThe example below watches a key, reads its current value, queues an update\ninside `MULTI`, and then checks `TransactionResult.wasDiscarded()` after\n`EXEC`:\n\nOptimistic locking: Use WATCH with EXEC and check whether Lettuce discarded the transaction\n\n**Difficulty:** Intermediate\n\n**Available in:** Java, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce)\n\n##### Java\n\n[code example]\n\n##### Java (Asynchronous - Lettuce)\n\n[code example]\n\n##### Java (Reactive - Lettuce)\n\n[code example]"
    }
  ],
  "examples": [
    {
      "id": "execute-a-pipeline-ex0",
      "language": "java",
      "code": "// Lettuce pipelines commands by buffering them on the client while\n            // auto-flushing is disabled. Use the asynchronous API to obtain a\n            // `RedisFuture` for each buffered command, then read the results\n            // after the batch has been flushed to the server.\n            RedisAsyncCommands<String, String> async = connection.async();\n            connection.setAutoFlushCommands(false);\n\n            for (int i = 0; i < 5; i++) {\n                async.set(\"seat:\" + i, \"#\" + i);\n            }\n\n            RedisFuture<String> seat0 = async.get(\"seat:0\");\n            RedisFuture<String> seat3 = async.get(\"seat:3\");\n            RedisFuture<String> seat4 = async.get(\"seat:4\");\n\n            connection.flushCommands();\n            connection.setAutoFlushCommands(true);\n\n            System.out.println(seat0.get()); // >>> #0\n            System.out.println(seat3.get()); // >>> #3\n            System.out.println(seat4.get()); // >>> #4",
      "section_id": "execute-a-pipeline"
    },
    {
      "id": "execute-a-pipeline-ex1",
      "language": "java",
      "code": "// Disable auto-flushing to buffer the commands on the client, then\n            // flush them to the server as a single batch. Each command returns\n            // a `RedisFuture` that completes when its response arrives.\n            connection.setAutoFlushCommands(false);\n\n            for (int i = 0; i < 5; i++) {\n                async.set(\"seat:\" + i, \"#\" + i);\n            }\n\n            RedisFuture<String> seat0 = async.get(\"seat:0\");\n            RedisFuture<String> seat3 = async.get(\"seat:3\");\n            RedisFuture<String> seat4 = async.get(\"seat:4\");\n\n            connection.flushCommands();\n            connection.setAutoFlushCommands(true);\n\n            System.out.println(seat0.get()); // >>> #0\n            System.out.println(seat3.get()); // >>> #3\n            System.out.println(seat4.get()); // >>> #4",
      "section_id": "execute-a-pipeline"
    },
    {
      "id": "execute-a-pipeline-ex2",
      "language": "java",
      "code": "// Reactive commands are pipelined automatically: the writes below are\n            // sent to the server back to back, then the reads are sent as a batch\n            // once you subscribe (here, by calling `block()`).\n            List<String> seats = Flux.range(0, 5)\n                    .flatMap(i -> reactive.set(\"seat:\" + i, \"#\" + i))\n                    .thenMany(Flux.just(\"seat:0\", \"seat:3\", \"seat:4\")\n                            .flatMapSequential(reactive::get))\n                    .collectList()\n                    .block();\n\n            System.out.println(seats.get(0)); // >>> #0\n            System.out.println(seats.get(1)); // >>> #3\n            System.out.println(seats.get(2)); // >>> #4",
      "section_id": "execute-a-pipeline"
    },
    {
      "id": "execute-a-transaction-ex0",
      "language": "java",
      "code": "commands.multi();\n\n            commands.incrby(\"counter:1\", 1);\n            commands.incrby(\"counter:2\", 2);\n            commands.incrby(\"counter:3\", 3);\n\n            TransactionResult transResult = commands.exec();\n\n            System.out.println((Long) transResult.get(0)); // >>> 1\n            System.out.println((Long) transResult.get(1)); // >>> 2\n            System.out.println((Long) transResult.get(2)); // >>> 3",
      "section_id": "execute-a-transaction"
    },
    {
      "id": "execute-a-transaction-ex1",
      "language": "java",
      "code": "// Commands issued between `multi()` and `exec()` are queued on the\n            // server. Their futures only complete when `exec()` runs.\n            async.multi();\n\n            RedisFuture<Long> counter1 = async.incrby(\"counter:1\", 1);\n            RedisFuture<Long> counter2 = async.incrby(\"counter:2\", 2);\n            RedisFuture<Long> counter3 = async.incrby(\"counter:3\", 3);\n\n            RedisFuture<TransactionResult> execResult = async.exec();\n            execResult.get();\n\n            System.out.println(counter1.get()); // >>> 1\n            System.out.println(counter2.get()); // >>> 2\n            System.out.println(counter3.get()); // >>> 3",
      "section_id": "execute-a-transaction"
    },
    {
      "id": "execute-a-transaction-ex2",
      "language": "java",
      "code": "// Queue the commands between `multi()` and `exec()`. Subscribing to\n            // each one inside the `multi()` callback sends it to the server, where\n            // it is held until `exec()` runs the transaction.\n            TransactionResult transResult = reactive.multi()\n                    .flatMap(ok -> {\n                        reactive.incrby(\"counter:1\", 1).subscribe();\n                        reactive.incrby(\"counter:2\", 2).subscribe();\n                        reactive.incrby(\"counter:3\", 3).subscribe();\n                        return reactive.exec();\n                    })\n                    .block();\n\n            System.out.println((Long) transResult.get(0)); // >>> 1\n            System.out.println((Long) transResult.get(1)); // >>> 2\n            System.out.println((Long) transResult.get(2)); // >>> 3",
      "section_id": "execute-a-transaction"
    },
    {
      "id": "watch-keys-for-changes-ex0",
      "language": "java",
      "code": "commands.set(\"shellpath\", \"/usr/syscmds/\");\n\n            // Watch the key for changes while we prepare the update.\n            commands.watch(\"shellpath\");\n\n            String currentPath = commands.get(\"shellpath\");\n            String newPath = currentPath + \":/usr/mycmds/\";\n\n            commands.multi();\n            commands.set(\"shellpath\", newPath);\n\n            TransactionResult watchedResult = commands.exec();\n\n            // `exec()` returns an empty result that reports `wasDiscarded()` if\n            // the watched key changed before the transaction ran.\n            if (!watchedResult.wasDiscarded()) {\n                System.out.println(commands.get(\"shellpath\"));\n                // >>> /usr/syscmds/:/usr/mycmds/\n            }",
      "section_id": "watch-keys-for-changes"
    },
    {
      "id": "watch-keys-for-changes-ex1",
      "language": "java",
      "code": "async.set(\"shellpath\", \"/usr/syscmds/\").get();\n\n            // Watch the key for changes while we prepare the update.\n            async.watch(\"shellpath\").get();\n\n            String currentPath = async.get(\"shellpath\").get();\n            String newPath = currentPath + \":/usr/mycmds/\";\n\n            async.multi().get();\n            async.set(\"shellpath\", newPath);\n\n            TransactionResult watchedResult = async.exec().get();\n\n            // `exec()` reports `wasDiscarded()` if the watched key changed\n            // before the transaction ran.\n            if (!watchedResult.wasDiscarded()) {\n                System.out.println(async.get(\"shellpath\").get());\n                // >>> /usr/syscmds/:/usr/mycmds/\n            }",
      "section_id": "watch-keys-for-changes"
    },
    {
      "id": "watch-keys-for-changes-ex2",
      "language": "java",
      "code": "reactive.set(\"shellpath\", \"/usr/syscmds/\").block();\n\n            // Watch the key, read its current value, then queue the update in a\n            // transaction. `exec()` reports `wasDiscarded()` if the watched key\n            // changed before the transaction ran.\n            TransactionResult watchedResult = reactive.watch(\"shellpath\")\n                    .then(reactive.get(\"shellpath\"))\n                    .flatMap(currentPath -> reactive.multi()\n                            .flatMap(ok -> {\n                                reactive.set(\"shellpath\", currentPath + \":/usr/mycmds/\").subscribe();\n                                return reactive.exec();\n                            }))\n                    .block();\n\n            if (!watchedResult.wasDiscarded()) {\n                System.out.println(reactive.get(\"shellpath\").block());\n                // >>> /usr/syscmds/:/usr/mycmds/\n            }",
      "section_id": "watch-keys-for-changes"
    }
  ]
}
