{
  "id": "java-jedis",
  "title": "Redis pub/sub with Jedis",
  "url": "https://redis.io/docs/latest/develop/use-cases/pub-sub/java-jedis/",
  "summary": "Implement Redis pub/sub messaging in Java with Jedis",
  "tags": [
    "docs",
    "develop",
    "stack",
    "oss",
    "rs",
    "rc"
  ],
  "last_updated": "2026-05-14T08:58:05-05:00",
  "children": [],
  "page_type": "content",
  "content_hash": "315ec14445959955b36a045d5b9effd545c01c60ef55bc2acb2ca281c7118957",
  "sections": [
    {
      "id": "overview",
      "title": "Overview",
      "role": "overview",
      "text": "This guide shows you how to implement a Redis-backed pub/sub broadcaster in Java with [`Jedis`](https://redis.io/docs/latest/develop/clients/jedis). It includes a small local web server built with Java's built-in `HttpServer` so you can publish messages to named channels, add and remove subscribers live, and watch Redis fan out each message to every interested listener."
    },
    {
      "id": "overview",
      "title": "Overview",
      "role": "overview",
      "text": "Pub/sub lets your application broadcast events — chat messages, cache invalidation signals, presence updates, notifications — to many consumers without per-pair wiring. The publisher names a *channel*; every client currently subscribed to that channel receives the message, in publish order, with sub-millisecond fan-out.\n\nThat gives you:\n\n* Many-to-many event delivery with no message storage cost in Redis\n* Exact-match subscriptions (`SUBSCRIBE orders:new`) for known topics\n* Pattern subscriptions (`PSUBSCRIBE notifications:*`) for whole topic hierarchies\n* Live server-side introspection through `PUBSUB CHANNELS`, `PUBSUB NUMSUB`, and `PUBSUB NUMPAT`\n* At-most-once delivery: subscribers that are offline when a message is published miss it, so durable state should live in keys or a Stream, not in the pub/sub channel itself\n\nIn this example, the publisher side calls `PUBLISH` with a JSON-encoded body and counts how many subscribers Redis reported delivering to. Each in-process subscriber owns its own `Jedis` connection from the pool and a dedicated worker thread that pumps incoming messages into a buffer."
    },
    {
      "id": "how-it-works",
      "title": "How it works",
      "role": "content",
      "text": "The flow looks like this:\n\n1. The application calls `hub.subscribe(name, channels)` or `hub.psubscribe(name, patterns)`\n2. The helper acquires a dedicated `Jedis` connection from the pool, builds a `JedisPubSub` listener, and starts a daemon thread that calls `jedis.subscribe(listener, channels...)` (or `psubscribe(...)`) — both calls block for the lifetime of the subscription\n3. The application (or another process) calls `hub.publish(channel, message)`\n4. Redis fans the message out over every subscribing client's open socket\n5. The listener's `onMessage` (or `onPMessage`) callback fires on the subscriber thread, wraps the raw message as a `ReceivedMessage`, and appends it to a per-subscriber ring buffer\n6. The publisher receives the integer subscriber count back from `PUBLISH`, which is the number of clients Redis delivered to right then\n\nPattern subscriptions match channels by glob (`*`, `?`, `[abc]`). A single message that matches both an exact subscription and a pattern subscription is delivered twice — once as an `onMessage` and once as an `onPMessage`."
    },
    {
      "id": "the-pub-sub-hub-helper",
      "title": "The pub/sub hub helper",
      "role": "content",
      "text": "The `RedisPubSubHub` class wraps the publish, subscribe, and introspection operations\n([source](https://github.com/redis/docs/blob/main/content/develop/use-cases/pub-sub/java-jedis/RedisPubSubHub.java)):\n\n[code example]"
    },
    {
      "id": "data-model",
      "title": "Data model",
      "role": "content",
      "text": "Pub/sub has no Redis keyspace footprint of its own — channels are server-side routing entries, not stored values. The hub keeps its own bookkeeping in process memory:\n\n[code example]\n\nThe implementation uses:\n\n* [`PUBLISH`](https://redis.io/docs/latest/commands/publish) to fan a JSON-encoded message out to every subscriber of a channel\n* [`SUBSCRIBE`](https://redis.io/docs/latest/commands/subscribe) for exact-match subscribers\n* [`PSUBSCRIBE`](https://redis.io/docs/latest/commands/psubscribe) for glob-style pattern subscribers\n* [`PUBSUB CHANNELS`](https://redis.io/docs/latest/commands/pubsub-channels) to list the channels with at least one active exact-match subscriber\n* [`PUBSUB NUMSUB`](https://redis.io/docs/latest/commands/pubsub-numsub) to count subscribers per channel\n* [`PUBSUB NUMPAT`](https://redis.io/docs/latest/commands/pubsub-numpat) to count active pattern subscriptions server-wide\n* Jedis' `JedisPubSub` listener subclass plus a dedicated daemon thread per subscriber so the blocking `subscribe()`/`psubscribe()` call does not freeze the rest of the application"
    },
    {
      "id": "publishing-messages",
      "title": "Publishing messages",
      "role": "content",
      "text": "`publish()` JSON-encodes the message body, calls `PUBLISH`, and updates the per-channel publish counter:\n\n[code example]\n\nThe integer returned by `PUBLISH` is what Redis itself reports — the number of subscribers (direct and pattern) that received the message in that call. It's a useful sanity check that the channel name is actually being listened to: a steady stream of `0`s means you have a typo somewhere or your subscriber crashed."
    },
    {
      "id": "subscribing-to-channels",
      "title": "Subscribing to channels",
      "role": "content",
      "text": "`subscribe()` creates a named in-process `Subscription` that owns its own `Jedis` connection and a dedicated worker thread:\n\n[code example]\n\nInside the `Subscription` constructor, the helper builds a `JedisPubSub` listener, starts the dispatch thread, and blocks until Redis has acknowledged every `SUBSCRIBE` so the very first publish can't race ahead:\n\n[code example]\n\nA few details matter here:\n\n* `jedis.subscribe(...)` and `jedis.psubscribe(...)` **block the calling thread** until the listener unsubscribes from every target. Running each subscription on its own thread is the only way to keep the rest of the application responsive — there is no \"run in background\" helper in Jedis.\n* Each `Subscription` gets its own `Jedis` connection from the pool. A connection that has issued `SUBSCRIBE` or `PSUBSCRIBE` is switched into subscribe-only mode, so other commands on the same connection would fail. Sharing one connection across subscribers would also couple their lifetimes — unsubscribing one would tear down the channel for the others.\n* The constructor waits on a `CountDownLatch` that's decremented once per target inside `onSubscribe` / `onPSubscribe`. Without it, the parent thread could return from `subscribe()` while the spawned worker thread is still about to send the SUBSCRIBE command — and a `PUBLISH` issued immediately afterwards would beat the subscription into Redis.\n* The `JedisPubSub` listener is the same object on both threads: it's safe to call `listener.unsubscribe()` from a different thread because Jedis ships the unsubscribe command back over the listener's own socket. After the unsubscribe round-trip completes, the blocking `subscribe()` call returns and the worker thread exits."
    },
    {
      "id": "pattern-subscriptions-with-psubscribe",
      "title": "Pattern subscriptions with PSUBSCRIBE",
      "role": "content",
      "text": "`psubscribe()` works the same way but routes messages through `PSUBSCRIBE` so each binding is a glob, not a literal channel name:\n\n[code example]\n\nWhen a published channel matches a pattern, the `onPMessage` callback receives both the matched channel and the original pattern:\n\n[code example]\n\nThat distinction is useful for routing: a pattern subscriber can do one thing for the whole hierarchy (e.g., increment a counter) and dispatch on the specific channel within its callback (e.g., \"invalidate this region's cache\")."
    },
    {
      "id": "inspecting-active-subscribers",
      "title": "Inspecting active subscribers",
      "role": "content",
      "text": "Redis exposes a small set of pub/sub introspection commands that report on subscriber state without traversing any keyspace:\n\n[code example]\n\n`PUBSUB CHANNELS` only reports channels with at least one exact-match subscriber — pattern subscribers do not appear here. That's a deliberate Redis design choice: a glob like `*` would otherwise show up as a subscriber to every conceivable channel. `PUBSUB NUMPAT` covers the pattern side as a single global count.\n\nEach of these commands acquires a regular (non-subscribe) `Jedis` connection from the pool with try-with-resources, so the bookkeeping calls never compete with the dedicated subscriber connections."
    },
    {
      "id": "stats-and-history",
      "title": "Stats and history",
      "role": "content",
      "text": "`stats()` reports publish and receive counters plus the size of the subscription registry:\n\n[code example]\n\n`delivered_total` is what Redis itself counted; `received_total` is what this process's in-memory subscribers saw. In a single-process demo they should track each other closely — a sustained divergence usually means a callback raised, or a subscriber crashed while a publisher kept publishing. (Pub/sub is at-most-once: if your subscriber wasn't connected at publish time, the message is gone.)"
    },
    {
      "id": "prerequisites",
      "title": "Prerequisites",
      "role": "content",
      "text": "* Redis 6.2 or later running locally on the default port (6379). Earlier versions still work for plain `PUBLISH`/`SUBSCRIBE`; `PUBSUB NUMPAT` is older than that.\n* Java 17 or later.\n* Jedis 5.x on the classpath. The smallest workable classpath is the Jedis jar plus its two transitive dependencies, `commons-pool2` and `slf4j-api`.\n\nIf you use Maven:\n\n[code example]\n\nIf you use Gradle:\n\n[code example]"
    },
    {
      "id": "running-the-demo",
      "title": "Running the demo",
      "role": "content",
      "text": ""
    },
    {
      "id": "get-the-source-files",
      "title": "Get the source files",
      "role": "content",
      "text": "The demo consists of three Java source files. Download them from the [`java-jedis` source folder](https://github.com/redis/docs/tree/main/content/develop/use-cases/pub-sub/java-jedis) on GitHub, or grab them with `curl`:\n\n[code example]\n\nYou also need the three jars on the classpath. Grab them from [Maven Central](https://search.maven.org/) — search for `jedis`, `commons-pool2`, and `slf4j-api`, then download each jar from the artefact page — or copy them out of an existing local `~/.m2/repository` checkout if you've used these libraries before:\n\n* `jedis-5.1.2.jar`\n* `commons-pool2-2.12.0.jar`\n* `slf4j-api-2.0.13.jar`\n\n(Any 5.x Jedis release works; the transitive versions of `commons-pool2` and `slf4j-api` are pinned by the Jedis POM, so use the versions listed in the Jedis 5.1.2 [Maven page](https://search.maven.org/artifact/redis.clients/jedis/5.1.2/jar) if you want an exact match. Other recent patch versions of either library work fine.)"
    },
    {
      "id": "start-the-demo-server",
      "title": "Start the demo server",
      "role": "content",
      "text": "A local demo server is included to show the hub in action ([source](https://github.com/redis/docs/blob/main/content/develop/use-cases/pub-sub/java-jedis/DemoServer.java)). Compile and run with `javac` and `java`, listing each jar on the classpath:\n\n[code example]\n\nYou should see:\n\n[code example]\n\nOpen [http://127.0.0.1:8098](http://127.0.0.1:8098) in a browser. You can:\n\n* Publish messages of any text to any channel name in any batch size.\n* Add named subscribers that listen on either a specific channel (`orders:new`) or a glob pattern (`notifications:*`). A single subscriber can listen on multiple targets — enter them comma-separated.\n* Watch each subscriber's incoming-message panel update every 800 ms.\n* See the server-side view: `PUBSUB CHANNELS` lists exact-match channels with subscribers, `PUBSUB NUMSUB` gives per-channel counts, and `PUBSUB NUMPAT` counts active pattern subscriptions.\n* Click **Reset** to drop every subscription, zero the counters, and re-seed the three default subscribers.\n\nIf your Redis server is running elsewhere, start the demo with `--redis-host` and `--redis-port`."
    },
    {
      "id": "production-usage",
      "title": "Production usage",
      "role": "content",
      "text": ""
    },
    {
      "id": "pub-sub-is-at-most-once-pair-it-with-durable-state-if-you-need-replay",
      "title": "Pub/sub is at-most-once — pair it with durable state if you need replay",
      "role": "content",
      "text": "A subscriber that's offline when a message is published misses it permanently. For events you can't afford to lose, write the durable record (the order row, the cache key version, the audit log entry) to its primary store, then `PUBLISH` a notification so live consumers can pick it up immediately. On reconnect, consumers reconcile by reading the durable store, not by waiting for missed pub/sub messages. If you actually need replay or at-least-once delivery, switch to [Redis Streams](https://redis.io/docs/latest/develop/data-types/streams) with consumer groups."
    },
    {
      "id": "give-every-subscriber-its-own-jedis-connection",
      "title": "Give every subscriber its own Jedis connection",
      "role": "content",
      "text": "A `Jedis` instance that has called `subscribe(...)` or `psubscribe(...)` is in subscribe-only mode: any other command on the same connection will fail. Always pull the subscriber's connection from the pool with `pool.getResource()` and dedicate it to that subscriber until it's torn down. The helper does this for you — every `Subscription` opens a fresh connection and owns it for the subscription's lifetime — but if you write your own subscriber code, the same rule applies.\n\nA related corollary: never let the pool size cap your subscriber count. If you have 100 long-lived subscribers in one process, `JedisPoolConfig.setMaxTotal(...)` needs to allow at least 100 connections for the subscribers plus whatever your publishers and bookkeeping calls need. Set it generously; idle subscriber connections cost almost nothing on the Redis side."
    },
    {
      "id": "unsubscribe-through-the-listener-not-the-connection",
      "title": "Unsubscribe through the listener, not the connection",
      "role": "content",
      "text": "`jedis.subscribe(listener, channels...)` blocks until the listener calls `listener.unsubscribe()` (or `listener.punsubscribe()` for a pattern subscription). It's safe to call those methods from any thread: Jedis ships the unsubscribe command back over the listener's socket, Redis acknowledges, and then the blocking `subscribe()` call returns and the worker thread exits.\n\nClosing the underlying `Jedis` instance directly works as a fallback — the socket read errors out and the blocking call returns — but it's a less clean shutdown and produces a stack trace in the logs. The helper does both: it calls `listener.unsubscribe()` first, then closes the connection from the worker thread's `finally` block as a belt-and-braces guard."
    },
    {
      "id": "choose-a-topic-naming-convention-up-front",
      "title": "Choose a topic naming convention up front",
      "role": "content",
      "text": "A flat namespace gets ugly fast — `email`, `email_high_priority`, `email_high_priority_billing`. Pick a colon-separated hierarchy (`notifications:billing:invoice`, `cache:invalidate:products:p-001`) so consumers can subscribe at the right level: a billing service uses `notifications:billing:*`, the audit logger uses `notifications:*`. Glob patterns are evaluated for every published message, so don't go wild with multiple wildcards on hot paths — `*:*:*` matches everything and costs more than a flat `notifications:*` would."
    },
    {
      "id": "don-t-do-heavy-work-in-onmessage-onpmessage",
      "title": "Don't do heavy work in `onMessage` / `onPMessage`",
      "role": "content",
      "text": "The `JedisPubSub` callbacks run on the subscriber thread that owns the connection. If a callback blocks (synchronous HTTP call, big computation, slow DB write), the next message waits behind it and the subscriber's effective throughput drops to whatever the callback's latency is. For heavier work, the callback should hand the message off to a worker pool (`Executors.newFixedThreadPool(...)`) or a queue, or — for true durable handoff — a [Redis Streams](https://redis.io/docs/latest/develop/data-types/streams) consumer group."
    },
    {
      "id": "tune-the-subscriber-buffer-for-your-traffic-shape",
      "title": "Tune the subscriber buffer for your traffic shape",
      "role": "content",
      "text": "The demo caps each subscriber's in-memory message buffer at 50. That's right for showing the recent activity in a UI, but a real subscriber typically processes each message and discards it — the buffer is only there for human inspection. If you keep a buffer, make sure it's bounded; an unbounded ring on a chatty pattern subscriber will eventually OOM the process."
    },
    {
      "id": "sharded-pub-sub-on-a-redis-cluster",
      "title": "Sharded pub/sub on a Redis Cluster",
      "role": "content",
      "text": "On a Redis Cluster, plain `PUBLISH` fans every message out to every node via the cluster bus, which becomes a hotspot at high throughput. Redis 7.0 added [sharded pub/sub](https://redis.io/docs/latest/develop/pubsub#sharded-pubsub): channels are hashed to slots, and `SPUBLISH` / `SSUBSCRIBE` only touch the shard that owns the slot. Jedis 5.x exposes `jedis.spublish(...)` and a `JedisShardedPubSub` listener subclass. If you're scaling pub/sub on a cluster, prefer the sharded commands and pick channel names whose hash distribution matches your traffic."
    },
    {
      "id": "inspect-pub-sub-state-directly-in-redis",
      "title": "Inspect pub/sub state directly in Redis",
      "role": "content",
      "text": "Because pub/sub has no keyspace, `KEYS`/`SCAN` won't show you anything. Use the introspection commands instead:\n\n[code example]\n\n`redis-cli` in subscribe mode only exits with `Ctrl-C` — it can't issue any other commands while subscribed."
    },
    {
      "id": "learn-more",
      "title": "Learn more",
      "role": "related",
      "text": "This example uses the following Redis commands:\n\n* [`PUBLISH`](https://redis.io/docs/latest/commands/publish) to fan a message out to every subscriber of a channel.\n* [`SUBSCRIBE`](https://redis.io/docs/latest/commands/subscribe) and [`UNSUBSCRIBE`](https://redis.io/docs/latest/commands/unsubscribe) for exact-match topic subscriptions.\n* [`PSUBSCRIBE`](https://redis.io/docs/latest/commands/psubscribe) and [`PUNSUBSCRIBE`](https://redis.io/docs/latest/commands/punsubscribe) for glob-style pattern subscriptions.\n* [`PUBSUB CHANNELS`](https://redis.io/docs/latest/commands/pubsub-channels) to list channels with at least one active exact-match subscriber.\n* [`PUBSUB NUMSUB`](https://redis.io/docs/latest/commands/pubsub-numsub) to count subscribers per named channel.\n* [`PUBSUB NUMPAT`](https://redis.io/docs/latest/commands/pubsub-numpat) to count active pattern subscriptions server-wide.\n\nSee the [Jedis documentation](https://redis.io/docs/latest/develop/clients/jedis) for full client reference, including the [`JedisPubSub` listener](https://github.com/redis/jedis/blob/master/src/main/java/redis/clients/jedis/JedisPubSub.java) abstract class."
    }
  ],
  "examples": [
    {
      "id": "the-pub-sub-hub-helper-ex0",
      "language": "java",
      "code": "import java.util.List;\nimport java.util.Map;\nimport redis.clients.jedis.JedisPool;\n\npublic class Main {\n    public static void main(String[] args) {\n        JedisPool pool = new JedisPool(\"localhost\", 6379);\n        RedisPubSubHub hub = new RedisPubSubHub(pool);\n\n        // Exact-match subscriber\n        hub.subscribe(\"orders-listener\", List.of(\"orders:new\"));\n\n        // Pattern subscriber covering an entire topic hierarchy\n        hub.psubscribe(\"all-notifications\", List.of(\"notifications:*\"));\n\n        // Publish — returns Redis' delivered count for this PUBLISH\n        int delivered = hub.publish(\"orders:new\", Map.of(\"order_id\", 42, \"total\", 199.0));\n        System.out.printf(\"Redis delivered to %d subscriber(s)%n\", delivered);\n\n        // Look at what each subscriber received\n        for (RedisPubSubHub.Subscription sub : hub.subscriptions()) {\n            System.out.printf(\"%s %d messages%n\", sub.name(), sub.receivedTotal());\n            for (RedisPubSubHub.ReceivedMessage m : sub.messages(5)) {\n                System.out.printf(\"   %s %s%n\", m.channel(), m.payload());\n            }\n        }\n\n        hub.unsubscribe(\"orders-listener\");\n        hub.shutdown();  // closes every remaining subscription\n    }\n}",
      "section_id": "the-pub-sub-hub-helper"
    },
    {
      "id": "data-model-ex0",
      "language": "text",
      "code": "RedisPubSubHub                          (in-process)\n  subscriptions             Map<String, Subscription>\n  publishedTotal            AtomicLong\n  deliveredTotal            AtomicLong\n  channelPublished          Map<String, Long>      (per-channel publish counts)\n\nSubscription                            (in-process, one per subscriber)\n  name                      String\n  targets                   List<String>            (channels or patterns)\n  isPattern                 boolean\n  buffer                    Deque<ReceivedMessage>  (capped, default 50)\n  received                  long\n  listener                  JedisPubSub             (subscribed on its own connection)\n  thread                    Thread                  (blocks inside jedis.subscribe())",
      "section_id": "data-model"
    },
    {
      "id": "publishing-messages-ex0",
      "language": "java",
      "code": "public int publish(String channel, Object message) {\n    String payload = JsonUtil.toJson(message);\n    long delivered;\n    try (Jedis jedis = pool.getResource()) {\n        delivered = jedis.publish(channel, payload);\n    }\n    publishedTotal.incrementAndGet();\n    deliveredTotal.addAndGet(delivered);\n    channelPublished.merge(channel, 1L, Long::sum);\n    return (int) delivered;\n}",
      "section_id": "publishing-messages"
    },
    {
      "id": "subscribing-to-channels-ex0",
      "language": "java",
      "code": "public Subscription subscribe(String name, List<String> channels) {\n    return register(name, channels, false);\n}",
      "section_id": "subscribing-to-channels"
    },
    {
      "id": "subscribing-to-channels-ex1",
      "language": "java",
      "code": "this.jedis = pool.getResource();\n\nfinal CountDownLatch ackLatch = new CountDownLatch(targets.size());\n\nthis.listener = new JedisPubSub() {\n    @Override public void onMessage(String channel, String message) {\n        dispatch(channel, null, message);\n    }\n    @Override public void onPMessage(String pattern, String channel, String message) {\n        dispatch(channel, pattern, message);\n    }\n    @Override public void onSubscribe(String channel, int n) { ackLatch.countDown(); }\n    @Override public void onPSubscribe(String pattern, int n) { ackLatch.countDown(); }\n};\n\nfinal String[] targetArr = this.targets.toArray(new String[0]);\nthis.thread = new Thread(() -> {\n    if (isPattern) {\n        jedis.psubscribe(listener, targetArr);\n    } else {\n        jedis.subscribe(listener, targetArr);\n    }\n}, \"pubsub-\" + name);\nthis.thread.setDaemon(true);\nthis.thread.start();\n\nif (!ackLatch.await(2, TimeUnit.SECONDS)) {\n    throw new RuntimeException(\"subscribe acknowledgement did not arrive for '\" + name + \"'\");\n}",
      "section_id": "subscribing-to-channels"
    },
    {
      "id": "pattern-subscriptions-with-psubscribe-ex0",
      "language": "java",
      "code": "hub.psubscribe(\"all-notifications\", List.of(\"notifications:*\"));\nhub.psubscribe(\"cache-invalidator\", List.of(\"cache:invalidate:*\"));",
      "section_id": "pattern-subscriptions-with-psubscribe"
    },
    {
      "id": "pattern-subscriptions-with-psubscribe-ex1",
      "language": "java",
      "code": "@Override\npublic void onPMessage(String pattern, String channel, String message) {\n    dispatch(channel, pattern, message);\n}",
      "section_id": "pattern-subscriptions-with-psubscribe"
    },
    {
      "id": "inspecting-active-subscribers-ex0",
      "language": "java",
      "code": "hub.activeChannels(\"*\");                  // PUBSUB CHANNELS *\nhub.channelSubscriberCounts(channels);    // PUBSUB NUMSUB ch1 ch2 ...\nhub.patternSubscriberCount();             // PUBSUB NUMPAT",
      "section_id": "inspecting-active-subscribers"
    },
    {
      "id": "stats-and-history-ex0",
      "language": "java",
      "code": "public Map<String, Object> stats() {\n    List<Subscription> subs = subscriptions();\n    long receivedTotal = 0;\n    for (Subscription sub : subs) {\n        receivedTotal += sub.receivedTotal();\n    }\n    Map<String, Object> out = new LinkedHashMap<>();\n    out.put(\"published_total\", publishedTotal.get());\n    out.put(\"delivered_total\", deliveredTotal.get());          // sum of PUBLISH return values\n    out.put(\"received_total\", receivedTotal);\n    out.put(\"active_subscriptions\", (long) subs.size());\n    out.put(\"channel_published\", new LinkedHashMap<>(channelPublished));\n    out.put(\"pattern_subscriptions\", patternSubscriberCount());\n    return out;\n}",
      "section_id": "stats-and-history"
    },
    {
      "id": "prerequisites-ex0",
      "language": "xml",
      "code": "<dependency>\n    <groupId>redis.clients</groupId>\n    <artifactId>jedis</artifactId>\n    <version>5.1.2</version>\n</dependency>",
      "section_id": "prerequisites"
    },
    {
      "id": "prerequisites-ex1",
      "language": "groovy",
      "code": "implementation 'redis.clients:jedis:5.1.2'",
      "section_id": "prerequisites"
    },
    {
      "id": "get-the-source-files-ex0",
      "language": "bash",
      "code": "mkdir pub-sub-demo && cd pub-sub-demo\nBASE=https://raw.githubusercontent.com/redis/docs/main/content/develop/use-cases/pub-sub/java-jedis\ncurl -O $BASE/RedisPubSubHub.java\ncurl -O $BASE/DemoServer.java\ncurl -O $BASE/JsonUtil.java",
      "section_id": "get-the-source-files"
    },
    {
      "id": "start-the-demo-server-ex0",
      "language": "bash",
      "code": "javac -cp jedis-5.1.2.jar:commons-pool2-2.12.0.jar:slf4j-api-2.0.13.jar \\\n      JsonUtil.java RedisPubSubHub.java DemoServer.java\n\njava  -cp .:jedis-5.1.2.jar:commons-pool2-2.12.0.jar:slf4j-api-2.0.13.jar \\\n      DemoServer --port 8098",
      "section_id": "start-the-demo-server"
    },
    {
      "id": "start-the-demo-server-ex1",
      "language": "text",
      "code": "Redis pub/sub demo server listening on http://127.0.0.1:8098\nUsing Redis at localhost:6379\nSeeded 3 default subscription(s)",
      "section_id": "start-the-demo-server"
    },
    {
      "id": "inspect-pub-sub-state-directly-in-redis-ex0",
      "language": "bash",
      "code": "# Which channels currently have at least one exact-match subscriber?\nredis-cli pubsub channels '*'\n\n# How many subscribers does each channel have?\nredis-cli pubsub numsub orders:new notifications:billing chat:lobby\n\n# How many active pattern subscriptions across the whole server?\nredis-cli pubsub numpat\n\n# Subscribe interactively from the CLI to watch traffic on a pattern\nredis-cli psubscribe 'orders:*'",
      "section_id": "inspect-pub-sub-state-directly-in-redis"
    }
  ]
}
