jedis Documentation

Java java & redis-client repository by @redis. AI agent rules & system prompts with 12k stars.

12,367 stars Java Java 27 files · ~59,257 tokens #java#jedis#redis#redis-client#redis-cluster
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy jedis on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name jedis -p 8474:8474 --restart always ${CLIENT_LIBS_TEST_IMAGE}:${CLIENT_LIBS_TEST_IMAGE_TAG:-${REDIS_STACK_VERSION:-${REDIS_VERSION:-}}}
Configured Services & Ports
⚙️ toxiproxy
image: ghcr.io/shopify/toxiproxy:2.8.0
🔌 Ports: 8474:8474, 29379:29379, 29380:29380, 26400:26400
⚙️ redis-failover-1
image: ${CLIENT_LIBS_TEST_IMAGE}:${CLIENT_LIBS_TEST_IMAGE_TAG:-${REDIS_VERSION:-}}
🔌 Ports: 9379:9379
⚙️ redis-failover-2
image: ${CLIENT_LIBS_TEST_IMAGE}:${CLIENT_LIBS_TEST_IMAGE_TAG:-${REDIS_VERSION:-}}
🔌 Ports: 9380:9380
⚙️ redis1-2-5-8-sentinel
image: ${CLIENT_LIBS_TEST_IMAGE}:${CLIENT_LIBS_TEST_IMAGE_TAG:-${REDIS_VERSION:-}}
🔌 Ports: 6379:6379, 6380:6380, 6383:6383, 6386:6386, 6390:6390, 6391:6391, 26379:26379, 36379:36379
⚙️ standalone2-sentinel
image: ${CLIENT_LIBS_TEST_IMAGE}:${CLIENT_LIBS_TEST_IMAGE_TAG:-${REDIS_VERSION:-}}
🔌 Ports: 6381:6381, 16381:16381, 6382:6382, 16382:16382, 26380:26380, 36380:36380, 26382:26382, 36382:36382
⚙️ sentinel-failover
image: ${CLIENT_LIBS_TEST_IMAGE}:${CLIENT_LIBS_TEST_IMAGE_TAG:-${REDIS_VERSION:-}}
🔌 Ports: 6384:6384, 6385:6385, 26381:26381, 36381:36381
⚙️ redis9-10
image: ${CLIENT_LIBS_TEST_IMAGE}:${CLIENT_LIBS_TEST_IMAGE_TAG:-${REDIS_VERSION:-}}
🔌 Ports: 6388:6388, 6389:6389
⚙️ redis-unavailable
image: ${CLIENT_LIBS_TEST_IMAGE}:${CLIENT_LIBS_TEST_IMAGE_TAG:-${REDIS_VERSION:-}}
🔌 Ports: 6400:6400
⚙️ cluster-unbound
image: ${CLIENT_LIBS_TEST_IMAGE}:${CLIENT_LIBS_TEST_IMAGE_TAG:-${REDIS_VERSION:-}}
🔌 Ports: 7379-7383:7379-7383
⚙️ cluster-stable
image: ${CLIENT_LIBS_TEST_IMAGE}:${CLIENT_LIBS_TEST_IMAGE_TAG:-${REDIS_VERSION:-}}
🔌 Ports: 7479-7484:7479-7484, 8479-8484:8479-8484
⚙️ jedis-stack
image: ${CLIENT_LIBS_TEST_IMAGE}:${CLIENT_LIBS_TEST_IMAGE_TAG:-${REDIS_STACK_VERSION:-${REDIS_VERSION:-}}}
🔌 Ports: 6479:6479
⚙️ standalone-mtls
image: ${CLIENT_LIBS_TEST_IMAGE}:${CLIENT_LIBS_TEST_IMAGE_TAG:-${REDIS_VERSION:-}}
🔌 Ports: 6790:6790
⚙️ cluster-mtls
image: ${CLIENT_LIBS_TEST_IMAGE}:${CLIENT_LIBS_TEST_IMAGE_TAG:-${REDIS_VERSION:-}}
🔌 Ports: 8579-8581:8579-8581
docker-compose.yml
yaml (docker-compose.yml)
x-client-libs-image: &client-libs-image
  image: "${CLIENT_LIBS_TEST_IMAGE}:${CLIENT_LIBS_TEST_IMAGE_TAG:-${REDIS_VERSION:-}}"
x-client-libs-stack-image: &client-libs-stack-image
  image: "${CLIENT_LIBS_TEST_IMAGE}:${CLIENT_LIBS_TEST_IMAGE_TAG:-${REDIS_STACK_VERSION:-${REDIS_VERSION:-}}}"

services:
  toxiproxy:
    image: ghcr.io/shopify/toxiproxy:2.8.0
    ports:
      - "8474:8474"   # Admin API
      - "29379:29379" # redis-failover-1 proxy
      - "29380:29380" # redis-failover-2 proxy
      - "26400:26400" # redis-unavailable proxy
  redis-failover-1:
    <<: *client-libs-image
    container_name: redis-failover-1
    environment:
      - PORT=9379
    ports:
      - 9379:9379
  redis-failover-2:
    <<: *client-libs-image
    container_name: redis-failover-2
    environment:
      - PORT=9380
    ports:
      - 9380:9380
  redis1-2-5-8-sentinel:
    sysctls:
      - net.ipv6.conf.all.disable_ipv6=1
    <<: *client-libs-image
    container_name: redis1-2-5-8-sentinel
    #network_mode: host
    environment:
      - REDIS_CLUSTER=no
      - REDIS_CLIENT_USER=deploy
      - REDIS_CLIENT_PASSWORD=verify
      - TLS_ENABLED=yes
    ports:
      - "6379:6379"
      - "6380:6380"
      - "6383:6383"
      - "6386:6386"
      - "6390:6390"
      - "6391:6391"
      - "26379:26379" # sentinel
      - "36379:36379" # sentinel tls
    command: ${ENABLE_MODULE_COMMAND_DIRECTIVE}
    volumes:
      -  ${REDIS_ENV_CONF_DIR}/redis1-2-5-8-sentinel/config:/redis/config:r
      -  ${REDIS_ENV_WORK_DIR}/redis1-2-5-8-sentinel/work:/redis/work:rw
  standalone2-sentinel:
    sysctls:
      - net.ipv6.conf.all.disable_ipv6=1
    <<: *client-libs-image
    container_name: standalone2-sentinel
    #network_mode: host
    environment:
      - REDIS_CLUSTER=no
      - TLS_ENABLED=yes
      - REDIS_PASSWORD=foobared
    ports:
      - "6381:6381"
      - "16381:16381"
      - "6382:6382"
      - "16382:16382"
      - "26380:26380" # sentinel-standalone2-1
      - "36380:36380" # sentinel tls
      - "26382:26382" # sentinel-standalone2-3
      - "36382:36382" # sentinel tls
    volumes:
      -  ${REDIS_ENV_CONF_DIR}/standalone2-sentinel/config:/redis/config:r
      -  ${REDIS_ENV_WORK_DIR}/standalone2-sentinel/work:/redis/work:rw

  sentinel-failover:
    sysctls:
      - net.ipv6.conf.all.disable_ipv6=1
    <<: *client-libs-image
    container_name: sentinel-standalone2-failover
    #network_mode: host
    environment:
      - REDIS_CLUSTER=no
      - REDIS_PASSWORD=foobared
    ports:
      - "6384:6384"
      - "6385:6385"
      - "26381:26381" # sentinel
      - "36381:36381" # sentinel tls
    volumes:
      -  ${REDIS_ENV_CONF_DIR}/sentinel-standalone2-failover/config:/redis/config:r
      -  ${REDIS_ENV_WORK_DIR}/sentinel-standalone2-failover/work:/redis/work:rw

  redis9-10:
    sysctls:
      - net.ipv6.conf.all.disable_ipv6=1
    <<: *client-libs-image
    container_name: redis9-10
    #network_mode: host
    environment:
      - REDIS_CLUSTER=no
    ports:
      - "6388:6388"
      - "6389:6389"
    volumes:
      - ${REDIS_ENV_CONF_DIR}/redis9-10/config:/redis/config:r
      - ${REDIS_ENV_WORK_DIR}/redis9-10/work:/redis/work:rw
  redis-unavailable:
    sysctls:
      - net.ipv6.conf.all.disable_ipv6=1
    <<: *client-libs-image
    container_name: redis-unavailable-1
    #network_mode: host
    environment:
      - REDIS_CLUSTER=no
      - PORT=6400
    ports:
      - "6400:6400"
    volumes:
      -  ${REDIS_ENV_WORK_DIR}/redis-unavailable/work:/redis/work:rw

  cluster-unbound:
    sysctls:
      - net.ipv6.conf.all.disable_ipv6=1
    <<: *client-libs-image
    container_name: cluster-unbound-1
    environment:
      - REDIS_PASSWORD=cluster
    ports:
      - "7379-7383:7379-7383"
    volumes:
      - ${REDIS_ENV_CONF_DIR}/cluster-unbound/config:/redis/config:r
      - ${REDIS_ENV_WORK_DIR}/cluster-unbound/work:/redis/work:rw

  #TLS endpoints of Cluster stable are used for TLS tests that do not require client authentication
  cluster-stable:
    sysctls:
      - net.ipv6.conf.all.disable_ipv6=1
    <<: *client-libs-image
    container_name: cluster-stable-1
    #network_mode: host
    # cluster-node-timeout must be generous here: this env has no failover tests, and a low
    # value lets brief main-thread stalls (e.g. bulk vector indexing) trigger a transient
    # CLUSTERDOWN that silently rejects writes mid-test.
    command: --cluster-preferred-endpoint-type hostname --cluster-announce-hostname "localhost" --cluster-node-timeout 5000 --tls-auth-clients no --save ""
    environment:
      - REDIS_CLUSTER=yes
      - REDIS_PASSWORD=cluster
      - PORT=7479
      - TLS_PORT=8479
      - NODES=6
      - REPLICAS=1
      - TLS_ENABLED=yes
      - TLS_AUTH_CLIENTS_USER=off
    ports:
      - "7479-7484:7479-7484"
      - "8479-8484:8479-8484"
    volumes:
      - ${REDIS_ENV_CONF_DIR}/cluster-stable/config:/redis/config:r
      - ${REDIS_ENV_WORK_DIR}/cluster-stable/work:/redis/work:rw

  jedis-stack:
    sysctls:
      - net.ipv6.conf.all.disable_ipv6=1
    <<: *client-libs-stack-image
    container_name: jedis-stack
    command: --save ""
    environment:
      - REDIS_CLUSTER=no
      - PORT=6479
    ports:
      - "6479:6479"
    volumes:
      -  ${REDIS_ENV_WORK_DIR}/jedis-stack/work:/redis/work:rw
#todo find a way to connect from mac os host to exposed unix socket in container
#  redis_uds:
#    <<: *client-libs-image
#    container_name: redis_uds
#    #network_mode: host
#    command: redis-server /etc/redis.conf
#    volumes:
#      - "./redis_uds/config/node-0/redis.conf:/etc/redis.conf"
#      - "./work/redis_uds/work:/tmp/docker/"

  # Standalone Redis instance with mTLS (mutual TLS) authentication
  # Uses TLS_CLIENT_CNS to generate client certificates for mTLS users
  # TLS_AUTH_CLIENTS_USER=CN (default) extracts username from client certificate CN
  # ACL users matching client certificate CNs are configured via --user directives
  standalone-mtls:
    sysctls:
      - net.ipv6.conf.all.disable_ipv6=1
    <<: *client-libs-image
    container_name: standalone-mtls
    command:
      - "--save"
      - ""
      - "--user"
      - "mtls-user1"
      - "on"
      - "nopass"
      - "~*"
      - "&*"
      - "+@all"
      - "--user"
      - "mtls-user2"
      - "on"
      - "nopass"
      - "~*"
      - "&*"
      - "+@all"
    environment:
      - REDIS_CLUSTER=no
      - TLS_ENABLED=yes
      - TLS_PORT=6790
      - PORT=6789
      - TLS_CLIENT_CNS=mtls-user1 mtls-user2 mtls-user-without-acl
    ports:
      # We don't expose non-TLS port on purpose
      - "6790:6790"
    volumes:
      - ${REDIS_ENV_WORK_DIR}/standalone-mtls/work:/redis/work:rw

  # Redis cluster with mTLS (mutual TLS) authentication
  # 3 nodes without replicas to minimize memory footprint
  # TLS_AUTH_CLIENTS_USER=CN (default) extracts username from client certificate CN
  # ACL users matching client certificate CNs are configured via --user directives
  cluster-mtls:
    sysctls:
      - net.ipv6.conf.all.disable_ipv6=1
    <<: *client-libs-image
    container_name: cluster-mtls
    command:
      - "--cluster-preferred-endpoint-type"
      - "hostname"
      - "--cluster-announce-hostname"
      - "localhost"
      - "--cluster-node-timeout"
      - "150"
      - "--save"
      - ""
      - "--user"
      - "mtls-user1"
      - "on"
      - "nopass"
      - "~*"
      - "&*"
      - "+@all"
      - "--user"
      - "mtls-user2"
      - "on"
      - "nopass"
      - "~*"
      - "&*"
      - "+@all"
    environment:
      - REDIS_CLUSTER=yes
      - TLS_ENABLED=yes
      - TLS_PORT=8579
      - PORT=7579
      - NODES=3
      - REPLICAS=0
      - TLS_CLIENT_CNS=mtls-user1 mtls-user2 mtls-user-without-acl
    ports:
      # We don't expose non-TLS ports on purpose
      - "8579-8581:8579-8581"
    volumes:
      - ${REDIS_ENV_WORK_DIR}/cluster-mtls/work:/redis/work:rw

Technical Documentation: redis/jedis

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (`README.md`)

Jedis






What is Jedis?

Jedis is a Java client for Redis designed for performance and ease of use.

Are you looking for a high-level library to handle object mapping? See redis-om-spring!

How do I Redis?

Learn for free at Redis University

Try the Redis Cloud

Dive in developer tutorials

Join the Redis community

Work at Redis

Supported Redis versions

The most recent version of this library supports redis version
7.2,
7.4,
8.0,
8.2,
8.4,
8.6,
8.8 and
8.10.

The table below highlights version compatibility of the most-recent library versions with Redis and JDK versions. Compatibility means communication features, and Redis command capabilities.

Jedis version Supported Redis versions JDK Compatibility
3.9+ 5.0 to 6.2 Family of releases 8, 11
>= 4.0 Version 5.0 to 7.2 Family of releases 8, 11, 17
>= 5.0 Version 6.0 to current 8, 11, 17, 21
>= 5.2 Version 7.2 to current 8, 11, 17, 21
>= 6.0 Version 7.2 to current 8, 11, 17, 21
>= 7.0 Version 7.2 to current 8, 11, 17, 21
>= 8.0 Version 7.2 to current 8, 11, 17, 21, 25

Getting started

To get started with Jedis, first add it as a dependency in your Java project. If you're using Maven, that looks like this:

xml
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>8.0.1</version>
</dependency>

To use the cutting-edge Jedis, check here.

Next, you'll need to connect to Redis. Consider installing a redis server with docker:

bash
docker run -p 6379:6379 -it redis:latest

You can instantiate a RedisClient like so:

java
RedisClient jedis = RedisClient.builder().hostAndPort("localhost", 6379).build();

Now you can send commands:

java
jedis.sadd("planets", "Venus");

Connecting to a Redis cluster

Jedis lets you connect to Redis Clusters, supporting the Redis Cluster Specification.
To do this, you'll need to connect using RedisClusterClient. See the example below:

java
Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
jedisClusterNodes.add(new HostAndPort("127.0.0.1", 7379));
jedisClusterNodes.add(new HostAndPort("127.0.0.1", 7380));
RedisClusterClient jedis = RedisClusterClient.builder().nodes(jedisClusterNodes).build();

Now you can use the RedisClusterClient instance and send commands like you would with a standard pooled connection:

java
jedis.sadd("planets", "Mars");

Support for Redis data types

Jedis includes support for all Redis data types and features such as
JSON and VectorSets.

Failover

Jedis supports retry and failover for your Redis deployments. This is useful when:

  1. You have more than one Redis deployment. This might include two independent Redis servers or two or more Redis databases replicated across multiple active-active Redis Enterprise clusters.
  2. You want your application to connect to one deployment at a time and to fail over to the next available deployment if the first deployment becomes unavailable.

For the complete failover configuration options and examples, see the Jedis failover docs.

Token-Based Authentication

Jedis supports Token-Based authentication (TBA) starting with 5.3.0 GA release. This feature is complemented by an extension library that enhances the developer experience and provides most of the components required for TBA functionality.

Notably, the extension library includes built-in support for Microsoft EntraID, offering a seamless integration as part of the generic solution.

For more details and examples, please refer to the Advanced Usage documentation.

Documentation

The Jedis documentation site contains several useful articles for using Jedis.

You can also check the latest Jedis Javadocs.

Some specific use-case examples can be found in redis.clients.jedis.examples
package
of the test source codes.

Troubleshooting

If you run into trouble or have any questions, we're here to help!

Hit us up on the Redis Discord Server or
Jedis GitHub Discussions.

Contributing

We'd love your contributions!

Bug reports are always welcome! You can open a bug report on GitHub.

To report a security vulnerability, please follow our Security Policy.

You can also contribute documentation -- or anything to improve Jedis. Please see
contribution guideline for more details.

License

Jedis is licensed under the MIT license.

Sponsorship


Overview (`docs/README.md`)

Jedis Documentation

This documentation uses MkDocs to generate the static site.

See mkdocs.yml for the configuration.

To develop the documentation locally, you can use the included Dockerfile to build a container with all the
dependencies, and run it to preview your changes:

bash
# in docs/
docker build -t squidfunk/mkdocs-material .
# cd ..
docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material

Overview (`src/test/java/redis/clients/jedis/codegen/README.md`)

Code Generators

This package contains code generation tools for the Jedis project. These are not tests and should not be executed as part of the test suite.

CommandFlagsRegistryGenerator

Automatically generates and updates the static flags registry in CommandObject.java by retrieving command metadata from a running Redis server.

Purpose

The CommandObject class uses a static registry to map Redis command names to their flags.

How It Works

  1. Connects to a Redis server (default: localhost:6379)
  2. Retrieves all command metadata using the COMMAND command
  3. Processes commands and subcommands, extracting their flags
  4. Groups commands by their flag combinations
  5. Generates a static initializer block with inline EnumSet creation
  6. Updates CommandObject.java automatically using regex pattern matching
  7. Saves a backup JSON file for offline use

Prerequisites

  • A running Redis server (version 7.0+ recommended for full command metadata)
  • The Redis server should have all modules loaded if you want to include module commands

When to Run

Run this generator whenever:

  • Upgrading to a new Redis version
  • New Redis modules are added to your server
  • Command flags are modified in Redis
  • You want to ensure the registry is up-to-date with your Redis server

Fallback Mode

If the generator cannot connect to Redis, it will automatically fall back to using the backup JSON file (redis_commands_flags.json) if available.

Output

The generator will:

  • ✓ Connect to Redis and retrieve command metadata
  • ✓ Process commands and subcommands
  • ✓ Group commands by flag combinations
  • ✓ Generate the complete static initializer block
  • ✓ Update src/main/java/redis/clients/jedis/CommandObject.java in-place
  • ✓ Save a backup JSON file for offline use
  • ✓ Preserve original command names (with spaces, dots, hyphens)

Overview (`src/test/jmh/redis/clients/jedis/benchmark/README.md`)

Jedis JMH Benchmarks

JMH (Java Microbenchmark Harness) benchmarks for Jedis performance testing.

Quick Start

Maven (CI/CD)

bash
mvn -Pjmh clean test                              # All benchmarks
mvn -Pjmh test -Djmh.includes="CRC16Benchmark"   # Specific suite

IDE (Development)

  1. Open JmhMain.java
  2. Uncomment desired suite: runAllBenchmarks(), runCRC16Benchmarks(), etc.
  3. Right-click → Run

Results

  • benchmarks.json - Machine-readable results
  • benchmark.log - Execution log

Available Benchmarks

protocol.ReadBenchmark (10 benchmarks)

RESP3 Protocol.read operations with optional client-side cache.

Categories:

  • Baseline reads (no cache): readSimpleString, readBulkString, readArray, readMultiBulkResponse
  • Cache-aware overhead: cacheAwareRead* variants (push-check path, no pushes present)
  • Mixed scenarios: readWith1PushMessage, readWith100PushMessages (push frames preceding a response)

Use case: Measure RESP parsing cost and the overhead introduced by client-side caching.

protocol.ReadPushesBenchmark (2 benchmarks)

Protocol.readPushes — client-side cache invalidation processing.

Methods:

  • drain1Pending - One readPushes call drains 1 pending push frame
  • drain1000Pending - One readPushes call drains 1000 pending push frames

Use case: Measure how readPushes per-call cost scales with burst size; reveals fixed-vs-amortized overhead.

protocol.SendCommandBenchmark (1 benchmark)

Protocol.sendCommand — RESP command encoding.

Methods:

  • measureSendCommand - Encode SET command (full path: CommandArguments + encode)

Use case: Measure command-encoding cost without I/O.

CRC16Benchmark (2 benchmarks)

Redis Cluster hash slot calculation.

Methods:

  • getSlotString - String-based calculation
  • getSlotBytes - byte[]-based calculation

Test data: Rotates through 8 key patterns (empty, short, long, hashtag, etc.)
Batch size: 128 ops (8 patterns × 16)

SafeEncoderBenchmark (3 benchmarks)

UTF-8 encoding/decoding performance.

Methods:

  • encodeStringToBytes - String → byte[]
  • decodeBytesToString - byte[] → String
  • encodeMany - Bulk encoding

Test data: Rotates through 6 string patterns (2-44 chars)
Batch size: 120 ops (6 patterns × 20)

jedis.GetSetBenchmark (4 benchmarks) ⚠️ Requires Redis

Jedis GET/SET operations over network.

Methods:

  • set - Write performance (foo=bar)
  • get - Read performance (foo=bar)
  • pipelinedSet - Pipelined write (1000 ops, sync every 100)
  • pipelinedGet - Pipelined read (1000 ops, sync every 100)

Requirements: Redis 6.0+ running on localhost:6379 (or configured endpoint)
Mode: Throughput (ops/sec)

redisclient.GetSetBenchmark (12 benchmarks) ⚠️ Requires Redis

RedisClient (pooled) GET/SET operations with concurrency testing.

Thread configurations:

  • redisclient.GetSetBenchmark$Threads1 - 1 thread (baseline)
  • redisclient.GetSetBenchmark$Threads8 - 8 threads (moderate concurrency)
  • redisclient.GetSetBenchmark$Threads64 - 64 threads (high concurrency)

Methods per configuration:

  • set - Write performance using pooled RedisClient
  • get - Read performance using pooled RedisClient
  • pipelinedSet - Pipelined write (1000 ops, sync every 100)
  • pipelinedGet - Pipelined read (1000 ops, sync every 100)

Pool configuration:

  • MaxTotal: 64 connections (sized for max thread count)
  • MaxIdle: 64, MinIdle: 8
  • Prevents pool contention from skewing results

Workload:

  • Same single key/value pair ("foo"/"bar") as jedis.GetSetBenchmark for direct comparability
  • 1-thread variant produces numbers comparable to jedis.GetSetBenchmark
  • 8/64-thread variants exercise pool concurrency on the same workload

Requirements: Redis 6.0+ running on localhost:6379 (or configured endpoint)
Mode: Throughput (ops/sec)
Purpose: Test connection pool efficiency and pipelining under different load levels


Configuration

JMH Settings:

  • Forks: 1
  • Warmup: 3 iterations × 1s
  • Measurement: 5 iterations × 1s
  • Output: Average time (ns/op)

Understanding Results

text
Benchmark                        Mode  Cnt   Score   Error  Units
CRC16Benchmark.getSlotString     avgt    5  47.123 ± 2.456  ns/op
  • Score: Average time per operation (lower = better)
  • Error: 99.9% confidence interval (< 10% = good)
  • Units: ns/op (nanoseconds per operation)

Good result: Error < 10% of score
High variance: Error > 20% of score (need more iterations or batching)


Notes

  • Batching: Fast operations (< 100 ns) use batching to reduce JMH overhead
  • Test patterns: Batch size is always a multiple of pattern count for consistent results
  • IDE runner: JmhMain.java provides easy IDE execution
  • Redis required: jedis.GetSetBenchmark and redisclient.GetSetBenchmark require a running Redis instance (localhost:6379)
  • CI/CD: GitHub Actions runs benchmarks nightly, results published to gh-pages

Redis Client Components Overview (`docs/redis-client-components-overview.md`)

Redis Client Components Overview

Overview

This document provides a high-level overview of the Jedis client architecture, focusing on RedisClient, RedisClusterClient, RedisSentinelClient, and the experimental MultiDbClient. The architecture is built on two core abstractions:

  1. ConnectionProvider - Manages connections to Redis servers
  2. CommandExecutor - Executes Redis commands with various strategies (retry, cluster routing, circuit breaker)

All client implementations extend UnifiedJedis, which delegates command execution to a CommandExecutor that uses a ConnectionProvider to obtain connections.


Architecture Diagram

mermaid
flowchart TB

    subgraph ClientLayer["Client Layer"]
        RC["RedisClient<br/>(Standalone)"]
        RCC["RedisClusterClient<br/>(Cluster)"]
        RSC["RedisSentinelClient<br/>(Sentinel)"]
        MDC["MultiDbClient<br/>(Multi-DB Failover)"]
    end

    UJ["UnifiedJedis<br/><br/>- executor<br/>- provider<br/>- cache"]

    CE["CommandExecutor"]
    CP["ConnectionProvider"]

    RC --> UJ
    RCC --> UJ
    RSC --> UJ
    MDC --> UJ

    UJ --> CE
    UJ --> CP

Core Components

UnifiedJedis

Location: src/main/java/redis/clients/jedis/UnifiedJedis.java

Responsibilities:

  • Base implementation class for all Redis clients
  • Implements all Redis command interfaces
  • Delegates command execution to CommandExecutor
  • Manages client-side caching (CSC) when enabled

Key Fields:

java
protected final ConnectionProvider provider;
protected final CommandExecutor executor;
protected final CommandObjects commandObjects;
private final Cache cache;

Key Methods:

  • All Redis commands (GET, SET, HGET, etc.) - delegates to executor.executeCommand()
  • close() - closes the executor (which closes the provider)

CommandExecutor Hierarchy

Interface: CommandExecutor

Location: src/main/java/redis/clients/jedis/executors/CommandExecutor.java

java
public interface CommandExecutor extends AutoCloseable {
  <T> T executeCommand(CommandObject<T> commandObject);
}

Implementations

SimpleCommandExecutor

Location: src/main/java/redis/clients/jedis/executors/SimpleCommandExecutor.java

Characteristics:

  • Simplest implementation
  • Uses a single Connection directly
  • No retry logic
  • No connection pooling
  • Use Case: the deprecated UnifiedJedis(Connection) constructor (single fixed
    connection). Note: Pipeline and Transaction do not use a CommandExecutor at
    all — they queue commands directly on their Connection.

Execution Flow:

text
executeCommand() → connection.executeCommand()

DefaultCommandExecutor

Location: src/main/java/redis/clients/jedis/executors/DefaultCommandExecutor.java

Characteristics:

  • Uses ConnectionProvider to get connections
  • Connection pooling support
  • No retry logic
  • Use Case: RedisClient (standalone) without retry settings

Execution Flow:

text
executeCommand() → provider.getConnection() → connection.executeCommand() → close connection

RetryableCommandExecutor

Location: src/main/java/redis/clients/jedis/executors/RetryableCommandExecutor.java

Characteristics:

  • Retry logic with deadline-proportional backoff (the remaining time budget is
    spread across the remaining attempts)
  • Configurable maxAttempts and maxTotalRetriesDuration
  • Handles JedisConnectionException with backoff (sleeps only after 2+
    consecutive connection failures)
  • Deadline-based retry termination
  • Use Case: wired via the UnifiedJedis(provider, maxAttempts, maxTotalRetriesDuration) constructor. Not currently created by
    RedisClient's builder (see ClusterClientBuilder for builder-level retry
    settings).

Configuration:

java
RetryableCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration)

Execution Flow:

text
executeCommand()
  ├─ for (attempts = maxAttempts; attempts > 0; attempts--)
  │   ├─ provider.getConnection()
  │   ├─ connection.executeCommand()
  │   └─ catch JedisConnectionException
  │       ├─ log failure
  │       ├─ backoff sleep (after 2+ consecutive connection failures)
  │       └─ retry if deadline not exceeded
  └─ throw if all attempts exhausted

Backoff Formula:

text
sleepMillis = millisLeft / (attemptsLeft * (attemptsLeft + 1))

ClusterCommandExecutor

Location: src/main/java/redis/clients/jedis/executors/ClusterCommandExecutor.java

Characteristics:

  • Cluster-aware routing based on hash slots
  • Handles MOVED/ASK redirections
  • Supports broadcast commands (ALL_SHARDS, ALL_NODES)
  • Round-robin distribution for keyless commands
  • Retry logic with cluster topology awareness
  • Replica connection support
  • Use Case: RedisClusterClient

Configuration:

java
ClusterCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration, flags)

Request Policies:

  • DEFAULT - Single-shard routing based on key hash slot
  • ALL_SHARDS - Broadcast to all primary nodes
  • ALL_NODES - Broadcast to all nodes (including replicas)
  • MULTI_SHARD - Multi-key commands spanning shards
  • SPECIAL - Special handling (SCAN, FT.CURSOR, etc.)

Execution Flow:

text
executeCommand()
  ├─ Determine request policy from command flags
  ├─ ALL_SHARDS → broadcastCommand(primaryOnly=true)
  ├─ ALL_NODES → broadcastCommand(primaryOnly=false)
  └─ DEFAULT
      ├─ if keyless → executeKeylessCommand() (round-robin)
      └─ else → doExecuteCommand() (slot-based routing)
          ├─ Calculate hash slot from key
          ├─ provider.getConnection(slot)
          ├─ connection.executeCommand()
          └─ Handle MOVED/ASK redirections
              ├─ MOVED → update slot cache, retry
              └─ ASK → send ASKING, execute on target

Key Features:

  • Slot-based routing: Uses CRC16 hash to determine target shard
  • Redirection handling: Follows MOVED (permanent) and ASK (temporary) redirects
  • Topology refresh: Updates cluster topology on MOVED responses
  • Round-robin: Distributes keyless commands across cluster nodes

MultiDbCommandExecutor

Location: src/main/java/redis/clients/jedis/mcf/MultiDbCommandExecutor.java

Characteristics:

  • Circuit breaker pattern (Resilience4j)
  • Retry logic with exponential backoff
  • Multi-database failover (DR, backup, active-active)
  • Automatic failover on circuit breaker OPEN state
  • Health check integration
  • Experimental feature
  • Use Case: MultiDbClient for high availability

Configuration:

java
MultiDbCommandExecutor(multiDbConnectionProvider)

Execution Flow:

text
executeCommand()
  ├─ database = provider.getDatabase() (active database)
  ├─ Decorate with Resilience4j:
  │   ├─ withCircuitBreaker(database.circuitBreaker)
  │   ├─ withRetry(database.retry)
  │   └─ withFallback(exceptions, failoverHandler)
  ├─ Execute: handleExecuteCommand()
  │   ├─ database.getConnection()
  │   ├─ connection.executeCommand()
  │   └─ catch exceptions tracked by circuit breaker
  └─ On circuit breaker OPEN
      ├─ databaseFailover() (switch to next healthy database)
      └─ retry on new database

Circuit Breaker States:

  • CLOSED - Normal operation, tracking failures
  • OPEN - Too many failures, reject requests, trigger failover
  • HALF_OPEN - Testing if database recovered

Failover Strategy:

  • Weighted selection of healthy databases
  • Automatic failback to higher-priority databases
  • Health check probing (PING or lag-aware strategies)

ConnectionProvider Hierarchy

Interface: ConnectionProvider

Location: src/main/java/redis/clients/jedis/providers/ConnectionProvider.java

java
public interface ConnectionProvider extends AutoCloseable {
  Connection getConnection();
  Connection getConnection(CommandArguments args);
  Map<?, ?> getConnectionMap();
  Map<?, ?> getPrimaryNodesConnectionMap();
}

Implementations

ManagedConnectionProvider

Location: src/main/java/redis/clients/jedis/providers/ManagedConnectionProvider.java

Characteristics:

  • Single managed connection
  • No pooling
  • Connection set externally
  • Use Case: supplying an externally managed Connection to UnifiedJedis
    (advanced/custom wiring). Note: Pipeline and Transaction receive their
    Connection directly, without a provider.

Key Methods:

java
setConnection(Connection connection)  // Set the managed connection
getConnection() → connection          // Return the same connection

PooledConnectionProvider

Location: src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java

Characteristics:

  • Apache Commons Pool2 for connection pooling
  • Configurable pool settings (maxTotal, maxIdle, minIdle)
  • Connection validation
  • Use Case: RedisClient (standalone)

Configuration:

java
PooledConnectionProvider(hostAndPort, clientConfig, poolConfig)

Key Methods:

java
getConnection() → pool.getResource()
getConnection(args) → pool.getResource()  // Ignores args for standalone

Pool Configuration:

  • maxTotal - Maximum connections in pool
  • maxIdle - Maximum idle connections
  • minIdle - Minimum idle connections
  • testOnBorrow - Validate connection before use
  • testWhileIdle - Validate idle connections

ClusterConnectionProvider

Location: src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java

Characteristics:

  • Cluster-aware connection management
  • Slot-based routing (16384 slots)
  • Connection pool per cluster node
  • Automatic topology refresh
  • Replica connection support
  • Use Case: RedisClusterClient

Configuration:

java
ClusterConnectionProvider(clusterNodes, clientConfig, poolConfig)

Key Methods:

java
getConnection(CommandArguments args)
  ├─ Extract hash slots from keys
  ├─ Determine target slot
  └─ getConnectionFromSlot(slot) → pool for that slot's primary node

getConnection(HostAndPort node)
  └─ Get connection to specific node

getReplicaConnection(CommandArguments args)
  └─ Get connection to replica for read operations

Slot Mapping:

  • Maintains mapping: slot → HostAndPort → ConnectionPool
  • CRC16 hash algorithm: slot = CRC16(key) % 16384
  • Handles hash tags: {user}:123 → hash only user

Topology Management:

  • Initial topology discovery via CLUSTER SLOTS or CLUSTER NODES
  • Periodic refresh based on configuration
  • On-demand refresh on MOVED redirections

SentineledConnectionProvider

Location: src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java

Characteristics:

  • Sentinel-aware connection management
  • Automatic master failover detection
  • Listens to sentinel events (+switch-master)
  • Connection pool to current master
  • Use Case: RedisSentinelClient

Configuration:

java
SentineledConnectionProvider(masterName, masterClientConfig, poolConfig,
    sentinels, sentinelClientConfig)

Key Methods:

java
getConnection() → pool.getResource()  // Connection to current master
getCurrentMaster() → HostAndPort      // Current master endpoint

Failover Handling:

  • Subscribes to sentinel pub/sub channels
  • Listens for +switch-master events
  • Automatically recreates pool on master change
  • Validates new master before switching

MultiDbConnectionProvider

Location: src/main/java/redis/clients/jedis/mcf/MultiDbConnectionProvider.java

Characteristics:

  • Multiple database endpoints with isolated pools
  • Weighted database selection
  • Circuit breaker per database (Resilience4j)
  • Health check integration (PING, lag-aware)
  • Automatic failover and failback
  • Database switch event listeners
  • Experimental feature
  • Use Case: MultiDbClient for DR/backup/active-active

Configuration:

java
MultiDbConnectionProvider(multiDbConfig)
  ├─ DatabaseConfig[] - array of database endpoints
  ├─ RetryConfig - retry settings per database
  ├─ CircuitBreakerConfig - failure detection settings
  ├─ HealthCheckStrategy - PING or lag-aware
  └─ Failback settings

Key Concepts:

Database:

  • Each database has:
    • TrackingConnectionPool - connection pool
    • CircuitBreaker - failure tracking
    • Retry - retry configuration
    • weight - selection priority (higher weight preferred)
    • HealthCheck - health monitoring

Active Database Selection:

text
getDatabase() → activeDatabase (highest weight healthy database)

Failover Process:

text
1. Circuit breaker detects failures
2. Circuit breaker transitions to OPEN state
3. MultiDbCommandExecutor triggers failover
4. Select next healthy database by weight
5. Switch activeDatabase
6. Emit DatabaseSwitchEvent
7. Continue operations on new database

Failback Process:

text
1. Periodic health check (background thread)
2. Detect higher-priority database is healthy
3. Automatic failback to higher-priority database
4. Emit DatabaseSwitchEvent

Health Check Strategies:

  • PingStrategy - Simple PING command
  • LagAwareStrategy - Check replication lag (for active-active)

Key Methods:

java
getDatabase() → Database                    // Get active database
getConnection() → activeDatabase.getConnection()
setActiveDatabase(endpoint)                 // Manual failover
setDatabaseSwitchListener(listener)         // Listen to switch events

Client Implementations

RedisClient (Standalone)

Location: src/main/java/redis/clients/jedis/RedisClient.java

Builder: StandaloneClientBuilder

Default Configuration:

text
ConnectionProvider: PooledConnectionProvider
CommandExecutor: DefaultCommandExecutor

Usage:

java
RedisClient client = RedisClient.builder()
    .hostAndPort("localhost", 6379)
    .clientConfig(DefaultJedisClientConfig.builder()
        .password("secret")
        .build())
    .poolConfig(poolConfig)
    .build();

String value = client.get("key");
client.close();

Retries: builder-level retry settings (maxAttempts,
maxTotalRetriesDuration) are currently exposed only on ClusterClientBuilder.
For standalone retries, construct UnifiedJedis(provider, maxAttempts, maxTotalRetriesDuration) directly, which wires a RetryableCommandExecutor.


RedisClusterClient

Location: src/main/java/redis/clients/jedis/RedisClusterClient.java

Builder: ClusterClientBuilder

Default Configuration:

text
ConnectionProvider: ClusterConnectionProvider
CommandExecutor: ClusterCommandExecutor

Usage:

java
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("localhost", 7000));
nodes.add(new HostAndPort("localhost", 7001));

RedisClusterClient client = RedisClusterClient.builder()
    .nodes(nodes)
    .maxAttempts(5)
    .maxTotalRetriesDuration(Duration.ofSeconds(30))
    .build();

String value = client.get("key");
client.close();

Cluster-Specific Features:

  • Automatic slot-based routing
  • MOVED/ASK redirection handling
  • Broadcast commands (FLUSHALL, SCRIPT LOAD)
  • Multi-key operations validation

RedisSentinelClient

Location: src/main/java/redis/clients/jedis/RedisSentinelClient.java

Builder: SentinelClientBuilder

Default Configuration:

text
ConnectionProvider: SentineledConnectionProvider
CommandExecutor: DefaultCommandExecutor

Usage:

java
Set<HostAndPort> sentinels = new HashSet<>();
sentinels.add(new HostAndPort("localhost", 26379));
sentinels.add(new HostAndPort("localhost", 26380));

RedisSentinelClient client = RedisSentinelClient.builder()
    .masterName("mymaster")
    .sentinels(sentinels)
    .build();

String value = client.get("key");
client.close();

Sentinel-Specific Features:

  • Master discovery through sentinels
  • Automatic reconnection to the new master on +switch-master
  • Separate sentinelClientConfig(...) for connecting to the sentinel nodes

MultiDbClient (Experimental)

Location: src/main/java/redis/clients/jedis/MultiDbClient.java

Builder: MultiDbClientBuilder

Default Configuration:

text
ConnectionProvider: MultiDbConnectionProvider
CommandExecutor: MultiDbCommandExecutor

Usage:

java
DatabaseConfig primary = DatabaseConfig.builder(
        new HostAndPort("primary.redis.com", 6379), clientConfig)
    .weight(100.0f)  // Highest priority
    .build();

DatabaseConfig dr = DatabaseConfig.builder(
        new HostAndPort("dr.redis.com", 6379), clientConfig)
    .weight(50.0f)   // Lower priority
    .build();

MultiDbConfig multiDbConfig = MultiDbConfig.builder()
    .database(primary)
    .database(dr)
    .failureDetector(CircuitBreakerConfig.builder()
        .failureRateThreshold(50.0f)
        .build())
    .commandRetry(RetryConfig.builder()
        .maxAttempts(3)
        .build())
    .failbackCheckInterval(30_000)  // milliseconds
    .build();

MultiDbClient client = MultiDbClient.builder()
    .multiDbConfig(multiDbConfig)
    .databaseSwitchListener(event -> {
        System.out.println("Switched to: " + event.getEndpoint());
    })
    .build();

String value = client.get("key");  // Automatically fails over on errors
client.close();

Multi-DB Features:

  • Circuit breaker per database
  • Automatic failover on failures
  • Automatic failback to higher-priority databases
  • Health check monitoring
  • Database switch event notifications

Builder Pattern Hierarchy

AbstractClientBuilder

Location: src/main/java/redis/clients/jedis/builders/AbstractClientBuilder.java

Common Configuration:

  • clientConfig - JedisClientConfig (auth, SSL, timeout, protocol)
  • poolConfig - GenericObjectPoolConfig (pool settings)
  • cacheConfig / cache - Client-side caching configuration
  • connectionProvider / commandExecutor - custom component overrides

(Retry settings maxAttempts / maxTotalRetriesDuration are declared on
ClusterClientBuilder, not on the abstract base.)

Template Methods:

java
// Abstract — every builder must implement these
protected abstract T self();
protected abstract ConnectionProvider createDefaultConnectionProvider();
protected abstract C createClient();
protected abstract void validateSpecificConfiguration();

// Concrete — defaults to DefaultCommandExecutor; overridden by
// ClusterClientBuilder and MultiDbClientBuilder
protected CommandExecutor createDefaultCommandExecutor();

Builder Hierarchy:

text
AbstractClientBuilder
  ├─ StandaloneClientBuilder → RedisClient
  ├─ ClusterClientBuilder → RedisClusterClient
  ├─ SentinelClientBuilder → RedisSentinelClient
  └─ MultiDbClientBuilder → MultiDbClient

Execution Flow Examples

Example 1: Simple GET Command (RedisClient)

text
client.get("mykey")
  ↓
UnifiedJedis.get("mykey")
  ↓
executor.executeCommand(CommandObject<String>)
  ↓
DefaultCommandExecutor.executeCommand()
  ├─ provider.getConnection()
  │   ↓
  │   PooledConnectionProvider.getConnection()
  │   └─ pool.getResource() → Connection
  ├─ connection.executeCommand(GET mykey)
  └─ connection.close() (return to pool)
  ↓
Return "value"

Example 2: GET Command with Retry (UnifiedJedis with RetryableCommandExecutor)

text
client.get("mykey")
  ↓
UnifiedJedis.get("mykey")
  ↓
executor.executeCommand(CommandObject<String>)
  ↓
RetryableCommandExecutor.executeCommand()
  ├─ Attempt 1:
  │   ├─ provider.getConnection()
  │   ├─ connection.executeCommand(GET mykey)
  │   └─ JedisConnectionException thrown
  ├─ Backoff sleep (after 2+ consecutive connection failures)
  ├─ Attempt 2:
  │   ├─ provider.getConnection()
  │   ├─ connection.executeCommand(GET mykey)
  │   └─ Success!
  └─ Return "value"

Example 3: GET Command in Cluster (RedisClusterClient)

text
client.get("mykey")
  ↓
UnifiedJedis.get("mykey")
  ↓
executor.executeCommand(CommandObject<String>)
  ↓
ClusterCommandExecutor.executeCommand()
  ├─ Calculate slot: CRC16("mykey") % 16384 = 14687
  ├─ provider.getConnection(args)
  │   ↓
  │   ClusterConnectionProvider.getConnection(args)
  │   ├─ Extract slot from args: 14687
  │   ├─ Lookup node for slot: 192.168.1.10:7000
  │   └─ cache.getPool(node).getResource() → Connection
  ├─ connection.executeCommand(GET mykey)
  ├─ Receive MOVED 14687 192.168.1.11:7001
  ├─ Update slot cache
  ├─ provider.getConnection(192.168.1.11:7001)
  ├─ connection.executeCommand(GET mykey)
  └─ Return "value"

Example 4: GET Command with Multi-DB Failover (MultiDbClient)

text
client.get("mykey")
  ↓
UnifiedJedis.get("mykey")
  ↓
executor.executeCommand(CommandObject<String>)
  ↓
MultiDbCommandExecutor.executeCommand()
  ├─ database = provider.getDatabase() → primary (weight=1.0)
  ├─ Decorate with Resilience4j:
  │   ├─ CircuitBreaker (state=CLOSED)
  │   ├─ Retry (maxAttempts=3)
  │   └─ Fallback (on circuit breaker OPEN)
  ├─ Attempt 1 on primary:
  │   ├─ database.getConnection()
  │   ├─ connection.executeCommand(GET mykey)
  │   └─ JedisConnectionException (primary is down!)
  ├─ Circuit breaker records failure
  ├─ Retry attempt 2 on primary:
  │   └─ JedisConnectionException
  ├─ Circuit breaker records failure
  ├─ Retry attempt 3 on primary:
  │   └─ JedisConnectionException
  ├─ Circuit breaker transitions to OPEN
  ├─ Fallback triggered:
  │   ├─ databaseFailover()
  │   ├─ Select next healthy database → dr (weight=0.5)
  │   ├─ setActiveDatabase(dr)
  │   └─ Emit DatabaseSwitchEvent
  ├─ Recursive call: executeCommand() on dr
  │   ├─ database.getConnection()
  │   ├─ connection.executeCommand(GET mykey)
  │   └─ Success!
  └─ Return "value"

Advanced Usage (`docs/advanced-usage.md`)

Advanced Usage

Transactions

To do transactions in Jedis, you have to wrap operations in a transaction block, very similar to pipelining:

java
jedis.watch (key1, key2, ...);
Transaction t = jedis.multi();
t.set("foo", "bar");
t.exec();

Note: when you have any method that returns values, you have to do like this:

java
Transaction t = jedis.multi();
t.set("fool", "bar"); 
Response<String> result1 = t.get("fool");

t.zadd("foo", 1, "barowitch"); t.zadd("foo", 0, "barinsky"); t.zadd("foo", 0, "barikoviev");
Response<Set<String>> sose = t.zrange("foo", 0, -1);   // get the entire sortedset
t.exec();                                              // dont forget it

String foolbar = result1.get();                       // use Response.get() to retrieve things from a Response
int soseSize = sose.get().size();                      // on sose.get() you can directly call Set methods!

// List<Object> allResults = t.exec();                 // you could still get all results at once, as before

Note that a Response Object does not contain the result before t.exec() is called (it is a kind of a Future). Forgetting exec gives you exceptions. In the last lines, you see how transactions/pipelines were dealt with before version 2. You can still do it that way, but then you need to extract objects from a list, which contains also Redis status messages.

Note 2: Redis does not allow to use intermediate results of a transaction within that same transaction. This does not work:

java
// this does not work! Intra-transaction dependencies are not supported by Redis!
jedis.watch(...);
Transaction t = jedis.multi();
if(t.get("key1").equals("something"))
   t.set("key2", "value2");
else 
   t.set("key", "value");

However, there are some commands like setnx, that include such a conditional execution. Those are of course supported within transactions. You can build your own customized commands using EVAL / LUA scripting.

Pipelining

Sometimes you need to send a bunch of different commands. A very cool way to do that, and have better performance than doing it the naive way, is to use pipelining. This way you send commands without waiting for response, and you actually read the responses at the end, which is faster.

Here is how to do it:

java
Pipeline p = jedis.pipelined();
p.set("fool", "bar"); 
p.zadd("foo", 1, "barowitch");  p.zadd("foo", 0, "barinsky"); p.zadd("foo", 0, "barikoviev");
Response<String> pipeString = p.get("fool");
Response<Set<String>> sose = p.zrange("foo", 0, -1);
p.sync(); 

int soseSize = sose.get().size();
Set<String> setBack = sose.get();

For more explanations see code comments in the transaction section.

Avoiding pool waits with cluster pipelines

A cluster pipeline keeps one connection borrowed from every node it targets until sync() or close(). Concurrent pipelines that target nodes in different orders can exhaust small per-node pools and wait on one another indefinitely when the pool uses its default unbounded wait.

For workloads with concurrent multi-node pipelines, use a dedicated cluster client. Size each node pool for the expected number of concurrent pipelines and configure a finite maxWait so pool exhaustion fails within a known interval instead of waiting indefinitely:

java
ConnectionPoolConfig pipelinePoolConfig = new ConnectionPoolConfig();
pipelinePoolConfig.setMaxTotal(expectedConcurrentPipelines);
pipelinePoolConfig.setMaxWait(Duration.ofSeconds(1));

try (RedisClusterClient pipelineClient = RedisClusterClient.builder()
        .nodes(nodes)
        .clientConfig(clientConfig)
        .poolConfig(pipelinePoolConfig)
        .build();
    ClusterPipeline pipeline = pipelineClient.pipelined()) {
    // Append commands, then call sync() before reading responses.
}

Keep each pipeline single-shard when possible. This avoids borrowing multiple node connections at the same time.

Publish/Subscribe

To subscribe to a channel in Redis, create an instance of JedisPubSub and call subscribe on the Jedis instance:

java
class MyListener extends JedisPubSub {
        public void onMessage(String channel, String message) {
        }

        public void onSubscribe(String channel, int subscribedChannels) {
        }

        public void onUnsubscribe(String channel, int subscribedChannels) {
        }

        public void onPSubscribe(String pattern, int subscribedChannels) {
        }

        public void onPUnsubscribe(String pattern, int subscribedChannels) {
        }

        public void onPMessage(String pattern, String channel, String message) {
        }
}

MyListener l = new MyListener();

jedis.subscribe(l, "foo");

Note that subscribe is a blocking operation because it will poll Redis for responses on the thread that calls subscribe. A single JedisPubSub instance can be used to subscribe to multiple channels. You can call subscribe or psubscribe on an existing JedisPubSub instance to change your subscriptions.

Monitoring

To use the monitor command you can do something like the following:

java
new Thread(new Runnable() {
    public void run() {
        Jedis j = new Jedis("localhost");
        for (int i = 0; i < 100; i++) {
            j.incr("foobared");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
            }
        }
        j.disconnect();
    }
}).start();

jedis.monitor(new JedisMonitor() {
    public void onCommand(String command) {
        System.out.println(command);
    }
});

Token-Based Authentication

Starting with version 5.3.0 GA, Jedis supports token-based authentication. The redis-authx-entraid repository provides the necessary components that Jedis utilizes to enable this functionality.

Additionally, support for Microsoft EntraID has been fully implemented and is now available as an extension for Azure Managed Redis (AMR) and Azure Cache for Redis(ACR).

Using With Custom Identity Provider

Jedis provides a token-based authentication mechanism with a generic identity provider of your choice.
For custom use of this feature, you will need to provide an implementation of IdentityProvider and IdentityProviderConfig and configure it in the way Jedis expects.

You will have the required interfaces from transitive Jedis dependencies;

text
<dependency>
        <groupId>redis.clients.authentication</groupId>
        <artifactId>redis-authx-core</artifactId>
        <version>${version}</version>
    </dependency>

An example to get started:

java
public class YourCustomIdentityProviderConfig implements IdentityProviderConfig {
    ...
}

public class YourCustomIdentityProvider implements IdentityProvider {
    ...
}

Then configure Jedis like this:

java
IdentityProviderConfig yourCustomIdentityProviderConfig = new YourCustomIdentityProviderConfig();
TokenAuthConfig tokenAuthConfig = TokenAuthConfig.builder().identityProviderConfig(yourCustomIdentityProviderConfig);

JedisClientConfig config = DefaultJedisClientConfig.builder()
                .authXManager(new AuthXManager(tokenAuthConfig)).build();
...

Using With Microsoft EntraID

Extension for EntraID is fully integrated and ready to use with Azure Managed Redis(AMR) or Azure Cache for Redis(ACR). All you need is to add the EntraID dependency and code for configuration for chosen authentication type with Microsoft EntraID service.

To get started, add the redis-authx-entraid extension as dependency;

text
<dependency>
        <groupId>redis.clients.authentication</groupId>
        <artifactId>redis-authx-entraid</artifactId>
        <version>${version}</version>
    </dependency>

After adding the dependency, configure it using EntraIDTokenAuthConfigBuilder:

java
...
    TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder()
        .expirationRefreshRatio(0.8F)
        .clientId("yourClientId")
        .secret("yourClientSecret")
        .authority("yourAuthority")
        .scopes("yourRedisScopes").build();

    AuthXManager authXManager = new AuthXManager(tokenAuthConfig);

    JedisClientConfig config = DefaultJedisClientConfig.builder()
        .authXManager(authXManager).build();
...

Here you will see the AuthXManager class that is built into Jedis. Essentially it integrates the extension into Jedis and handles the authentication process.

Lifecycle: you create the AuthXManager, so you own it, just like the JedisClientConfig that carries it. Jedis starts it when the first client configured with it is created, and one manager may be shared by any number of clients. Jedis never stops it: call authXManager.stop() yourself once the last client using it has been closed, otherwise its token renewal threads keep running.
For other available configurations, detailed information and usage of Jedis with Microsoft EntraID, please refer to the official guide

Setting Up AMR or ACR with Microsoft EntraID:

To use Microsoft EntraID with AMR or ACR, for sure you will need to set up and configure your AMR/ACR services as well as Microsoft EntraID. The following resources provide useful information;

Azure Managed Redis

Azure Cache for Redis

Microsoft Entra ID for AMR authentication

Microsoft Entra ID for ACR authentication

Use Microsoft Entra

HostAndPortMapper

When running Jedis in certain network environments, such as behind a NAT gateway, or within container orchestration systems like Docker or Kubernetes, the host and port that the client needs to connect to might be different from the host and port that the Redis Cluster nodes report. To handle this discrepancy, Jedis provides the HostAndPortMapper interface.

This allows you to dynamically map the address reported by a Redis node to a different address that the client can actually reach. You can implement this either by creating a dedicated class or by using a concise lambda expression.

Example use case: NAT or Docker with different advertised ports

Suppose you run a Redis cluster inside Docker on a remote host.
Inside the cluster config, nodes announce addresses like:

text
172.18.0.2:6379
172.18.0.3:6379
172.18.0.4:6379

But externally, you reach them through the host IP with mapped ports:

text
my-redis.example.com:7001
my-redis.example.com:7002
my-redis.example.com:7003

Implementing with a Dedicated Class

You can provide your mapping logic by creating a class that implements the HostAndPortMapper interface. This approach is useful for more complex mapping logic or for reusability.

First, define your custom mapper class:

java
public class DockerNATMapper implements HostAndPortMapper {

    // Key: The address reported by Redis (internal).
    // Value: The address the client should connect to (external).
    private final Map<HostAndPort, HostAndPort> mapping;

    public DockerNATMapper(Map<HostAndPort, HostAndPort> mapping) {
        this.mapping = mapping;
    }

    @Override
    public HostAndPort getHostAndPort(HostAndPort hostAndPort) {
        return mapping.getOrDefault(hostAndPort, hostAndPort);
    }
}

Then, instantiate this class and pass it to the RedisClusterClient builder:

java
Map<HostAndPort, HostAndPort> nodeMapping = new HashMap<>();
nodeMapping.put(new HostAndPort("172.18.0.2", 6379), new HostAndPort("my-redis.example.com", 7001));
nodeMapping.put(new HostAndPort("172.18.0.3", 6379), new HostAndPort("my-redis.example.com", 7002));
nodeMapping.put(new HostAndPort("172.18.0.4", 6379), new HostAndPort("my-redis.example.com", 7002));

Set<HostAndPort> initialNodes = new HashSet<>();
// seed node
initialNodes.add(new HostAndPort("my-redis.example.com", 7001));

HostAndPortMapper mapper = new DockerNATMapper(nodeMapping);

JedisClientConfig jedisClientConfig = DefaultJedisClientConfig.builder()
        .user("myuser")
        .password("mypassword")
        .hostAndPortMapper(mapper)
        .build();

RedisClusterClient jedisCluster = RedisClusterClient.builder()
        .nodes(initialNodes)
        .clientConfig(jedisClientConfig)
        .build();

Now, when RedisClusterClient discovers a node at "172.18.0.2:6379", the mapper will translate it to "localhost:7001" before attempting to connect.

Implementing with a Lambda Expression

Since HostAndPortMapper is a functional interface (it has only one abstract method), you can also provide the implementation more concisely using a lambda expression. This is often preferred for simpler, inline mapping logic.

java
Map<HostAndPort, HostAndPort> nodeMapping = new HashMap<>();
nodeMapping.put(new HostAndPort("172.18.0.2", 6379), new HostAndPort("my-redis.example.com", 7001));
nodeMapping.put(new HostAndPort("172.18.0.3", 6379), new HostAndPort("my-redis.example.com", 7002));
nodeMapping.put(new HostAndPort("172.18.0.4", 6379), new HostAndPort("my-redis.example.com", 7002));

Set<HostAndPort> initialNodes = new HashSet<>();
initialNodes.add(new HostAndPort("my-redis.example.com", 7001));

HostAndPortMapper mapper = internalAddress -> nodeMapping.getOrDefault(internalAddress, internalAddress);

JedisClientConfig jedisClientConfig = DefaultJedisClientConfig.builder()
        .user("myuser")
        .password("mypassword")
        .hostAndPortMapper(mapper)
        .build();

RedisClusterClient jedisCluster = RedisClusterClient.builder()
        .nodes(initialNodes)
        .clientConfig(jedisClientConfig)
        .build();

Unix Domain Sockets

Jedis supports connecting to Redis via Unix Domain Sockets (UDS) instead of TCP. This can provide lower latency and higher throughput when the client and server are on the same machine, as it bypasses the TCP/IP stack.

To use UDS, implement the JedisSocketFactory interface with your preferred Unix socket library (e.g., junixsocket) and pass it to RedisClient via a custom ConnectionProvider.

Dependency

Add the junixsocket dependency to your project:

xml
<dependency>
    <groupId>com.kohlschutter.junixsocket</groupId>
    <artifactId>junixsocket-core</artifactId>
    <version>2.10.1</version>
</dependency>

Creating a UDS Socket Factory

java
import org.newsclub.net.unix.AFUNIXSocket;
import org.newsclub.net.unix.AFUNIXSocketAddress;

public class UdsSocketFactory implements JedisSocketFactory {

    private final File socketFile;

    public UdsSocketFactory(String socketPath) {
        this.socketFile = new File(socketPath);
    }

    @Override
    public Socket createSocket() throws JedisConnectionException {
        try {
            Socket socket = AFUNIXSocket.newStrictInstance();
            socket.connect(new AFUNIXSocketAddress(socketFile), Protocol.DEFAULT_TIMEOUT);
            return socket;
        } catch (IOException e) {
            throw new JedisConnectionException("Failed to create UDS connection.", e);
        }
    }
}

Connecting with RedisClient

Create a ConnectionFactory with your UDS socket factory and wrap it in a PooledConnectionProvider:

java
JedisSocketFactory socketFactory = new UdsSocketFactory("/tmp/redis.sock");
JedisClientConfig clientConfig = DefaultJedisClientConfig.builder().build();

ConnectionFactory connectionFactory = new ConnectionFactory(socketFactory, clientConfig);
PooledConnectionProvider provider = new PooledConnectionProvider(connectionFactory);

RedisClient client = RedisClient.builder()
    .connectionProvider(provider)
    .clientConfig(clientConfig)
    .build();

All RedisClient features work the same way over UDS, including connection pooling, RESP3, and client-side caching.

Miscellaneous

A note about String and Binary - what is native?

Redis/Jedis talks a lot about Strings. And here [[http://redis.io/topics/internals]] it says Strings are the basic building block of Redis. However, this stress on strings may be misleading. Redis' "String" refer to the C char type (8 bit), which is incompatible with Java Strings (16-bit). Redis sees only 8-bit blocks of data of predefined length, so normally it doesn't interpret the data (it's "binary safe"). Therefore in Java, byte[] data is "native", whereas Strings have to be encoded before being sent, and decoded after being retrieved by the SafeEncoder. This has some minor performance impact.
In short: if you have binary data, don't encode it into String, but use the binary versions.

A note on Redis' master/slave distribution

A Redis network consists of redis servers, which can be either masters or slaves. Slaves are synchronized to the master (master/slave replication). However, master and slaves look identical to a client, and slaves do accept write requests, but they will not be propagated "up-hill" and could eventually be overwritten by the master. It makes sense to route reads to slaves, and write demands to the master. Furthermore, being a slave doesn't prevent from being considered master by another slave.


Failover (`docs/failover.md`)

Automatic Failover and Failback with Jedis

API was significantly changed in 7.0.0. Please follow the migration guide below.

This feature is experimental and may change in future versions.

Jedis supports failover and failback for your Redis deployments. This is useful when:

  1. You have more than one Redis deployment. This might include two independent Redis servers or two or more Redis databases replicated across multiple active-active Redis Enterprise clusters.
  2. You want your application to connect to and use one deployment at a time.
  3. You want your application to fail over to the next available deployment if the current deployment becomes unavailable.
  4. You want your application to fail back to the original deployment when it becomes available again.

Jedis will fail over to a subsequent Redis deployment after reaching a configurable failure threshold.
This failure threshold is implemented using a circuit breaker pattern.

You can also configure Jedis to retry failed calls to Redis.
Once a maximum number of retries have been exhausted, the circuit breaker will record a failure.
When the circuit breaker reaches its failure threshold, a failover will be triggered on the subsequent operation.
In the background, Jedis executes configured health checks to determine when a Redis deployment is available again.
When this occurs, Jedis will fail back to the original deployment after a configurable grace period.

The remainder of this guide describes:

  • A basic failover and health check configuration
  • Supported retry, circuit breaker, failover and failback settings
  • Initialization policies that decide when the client is ready (since 7.3.0)
  • Failback and the database selection API
  • Dynamic weight management for runtime priority adjustments (since 7.4.0)
  • Dynamic database management for adding and removing databases at runtime

We recommend that you read this guide carefully and understand the configuration settings before enabling Jedis failover
in production.

Migration from 6.x to 7.x

In Jedis 6.x, failover was supported using special constructor for UnifiedJedis.
In Jedis 7.x, failover is supported using MultiDbClient and MultiDbConfig.builder:

java
// Jedis 6.x
JedisClientConfig config = DefaultJedisClientConfig.builder().user("cache").password("secret").build();

ClusterConfig[] clientConfigs = new ClusterConfig[2];
clientConfigs[0] = new ClusterConfig(new HostAndPort("redis-east.example.com", 14000), config);
clientConfigs[1] = new ClusterConfig(new HostAndPort("redis-west.example.com", 14000), config);

MultiClusterClientConfig.Builder builder = new MultiClusterClientConfig.Builder(clientConfigs);
// ...
MultiClusterPooledConnectionProvider provider = new MultiClusterPooledConnectionProvider(builder.build());
UnifiedJedis client = new UnifiedJedis(provider);

// Jedis 7.x
// MultiClusterClientConfig was renamed to MultiDbConfig and MultiDbClient with convenient builder was added
MultiDbConfig multiConfig = MultiDbConfig.builder()
        .database(DatabaseConfig.builder(east, config).weight(1.0f).build())
        .database(DatabaseConfig.builder(west, config).weight(0.5f).build())
        .build();
// Use MultiDbClient instead of UnifiedJedis
MultiDbClient multiDbClient = MultiDbClient.builder().multiDbConfig(multiConfig).build();

For more details on configuration options see sections below.

Installing optional dependencies

Jedis failover support is provided by optional dependencies.
To use failover, add the following dependencies to your project:

xml
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-all</artifactId>
    <version>1.7.1</version>
</dependency>
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-circuitbreaker</artifactId>
    <version>1.7.1</version>
</dependency>
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-retry</artifactId>
    <version>1.7.1</version>
</dependency>

Basic usage

To configure Jedis for failover, you specify a weighted list of Redis databases.
Jedis will connect to the Redis database in the list with the highest weight.
If the highest-weighted database becomes unavailable,
Jedis will attempt to connect to the database with the next highest weight in the list, and so on.

Database weights determine the priority for selecting which database becomes active. Weights can be configured at initialization and can also be changed dynamically at runtime (introduced in version 7.4.0), allowing you to adjust active database selection priorities without recreating the client.

Suppose you run two Redis deployments.
We'll call them redis-east and redis-west.
You want your application to first connect to redis-east.
If redis-east becomes unavailable, you want your application to connect to redis-west.

Let's look at one way of configuring Jedis for this scenario.

First, start by defining the initial configuration for each Redis database available and prioritize them using weights.

java
JedisClientConfig config = DefaultJedisClientConfig.builder()
        .user("cache").password("secret")
        .socketTimeoutMillis(5000).connectionTimeoutMillis(5000).build();

// Custom pool config per database can be provided
ConnectionPoolConfig poolConfig = new ConnectionPoolConfig();
poolConfig.setMaxTotal(8);
poolConfig.setMaxIdle(8);
poolConfig.setMinIdle(0);
poolConfig.setBlockWhenExhausted(true);
poolConfig.setMaxWait(Duration.ofSeconds(1));
poolConfig.setTestWhileIdle(true);
poolConfig.setTimeBetweenEvictionRuns(Duration.ofSeconds(1));

HostAndPort east = new HostAndPort("redis-east.example.com", 14000);
HostAndPort west = new HostAndPort("redis-west.example.com", 14000);

MultiDbConfig.Builder multiConfig = MultiDbConfig.builder()
        .database(DatabaseConfig.builder(east, config).connectionPoolConfig(poolConfig).weight(1.0f).build())
        .database(DatabaseConfig.builder(west, config).connectionPoolConfig(poolConfig).weight(0.5f).build());

The configuration above represents your two Redis deployments: redis-east and redis-west.

If you do not need a custom pool configuration or health check strategy, the shorter
database(endpoint, weight, clientConfig) overload builds the DatabaseConfig for you:

java
MultiDbConfig.Builder multiConfig = MultiDbConfig.builder()
        .database(east, 1.0f, config)
        .database(west, 0.5f, config);

Continue using the MultiDbConfig.Builder builder to set your preferred retry and failover configuration.
Then build a MultiDbClient:

text
/* Detailed source-code truncated for AI context efficiency. */

In the configuration here, we've set a sliding window of 10 seconds, a failure rate threshold of 50% and a minimum of 100 failures.
This means that a failover will be triggered only when, within the last 10 seconds, at least 100 calls to Redis have failed
and the failed calls make up at least 50% of all calls in that window. Both conditions must be met.

You can now use this MultiDbClient instance in your application to execute Redis commands.
MultiDbClient also provides failover-aware pipelines and transactions through pipelined() and multi();
see the Transactions page for the MultiDbTransaction specifics.

Configuration options

Under the hood, Jedis' failover support relies on resilience4j,
a fault-tolerance library that implements retry and circuit breakers.

Once you configure a MultiDbClient, each call to Redis is decorated with a resilience4j retry and circuit breaker.

By default, any call that throws a JedisConnectionException will be attempted up to 3 times (the initial call plus 2 retries).
If all attempts fail, the circuit breaker records a failure.

The circuit breaker keeps the outcome of recent calls in a time-based sliding window.
Jedis evaluates two thresholds against that window: a minimum number of failed calls and a failure rate.
When both are reached (e.g., at least 1000 calls failed within the last 2 seconds, and they make up at least 10% of all calls in that window),
the circuit breaker transitions from CLOSED to OPEN.
When this occurs, Jedis will switch to the healthy Redis database with the highest weight in its configuration.

The supported settings, and their default values, are described below.
You can configure any of these settings using the MultiDbConfig.Builder builder.
Refer the basic usage above for an example of this.

Retry configuration

Configuration for command retry behavior is encapsulated in MultiDbConfig.RetryConfig and provided using MultiDbConfig.Builder.commandRetry().
Jedis uses the following retry settings:

Setting Builder method Default value Description
Max attempts maxAttempts 3 Maximum number of attempts, including the initial call
Retry wait duration waitDuration 500 ms Number of milliseconds to wait between retry attempts
Wait duration backoff multiplier exponentialBackoffMultiplier 2 Exponential backoff factor multiplied against wait duration between retries. For example, with a wait duration of 1 second and a multiplier of 2, the retries would occur after 1s, 2s, 4s, 8s, 16s, and so on.
Retry included exception list includedExceptionList [JedisConnectionException] A list of Throwable classes that count as failures and should be retried.
Retry ignored exception list ignoreExceptionList null A list of Throwable classes to explicitly ignore for the purposes of retry.

To disable retry, set maxAttempts to 1.

Circuit breaker configuration

For failover, Jedis uses a circuit breaker to detect when a Redis database has failed.
Failover configuration is encapsulated in MultiDbConfig.CircuitBreakerConfig and can be provided using the MultiDbConfig.Builder.failureDetector().
Jedis uses the following circuit breaker settings:

Setting Builder method Default value Description
Sliding window size slidingWindowSize 2 Size of the time-based sliding window, in seconds, over which call outcomes are recorded.
Threshold min number of failures minNumOfFailures 1000 Minimum number of failed calls within the sliding window before the circuit breaker can trip. 0 disables this check so that only the failure rate is considered.
Failure rate threshold failureRateThreshold 10.0f Percentage of calls within the sliding window that must fail before the circuit breaker transitions to the OPEN state. 0.0f disables this check so that only the minimum number of failures is considered.
Circuit breaker included exception list includedExceptionList [JedisConnectionException] A list of Throwable classes that count as failures and add to the failure rate.
Circuit breaker ignored exception list ignoreExceptionList null A list of Throwable classes to explicitly ignore for failure rate calculations.

The circuit breaker opens only when both the minimum number of failures and the failure rate threshold are reached within the sliding window.
Once a failover has been triggered, the failed database's circuit breaker is kept open for the configured grace period
(see Failover and failback configuration) so that traffic does not return to it prematurely.

Failover and failback configuration

The following settings on MultiDbConfig.Builder control what happens once a failure has been detected,
and how Jedis returns to a preferred database once it recovers:

Setting Builder method Default value Description
Failback supported failbackSupported true Enables the periodic check that switches back to a healthy database with a higher weight than the active one.
Failback check interval failbackCheckInterval 120000 ms (2 min) How often the periodic failback check runs.
Grace period gracePeriod 60000 ms (1 min) How long a database stays disabled after it is marked unhealthy or its circuit breaker trips. During this period it is not considered for failover or failback, even if health checks report it healthy.
Fast failover fastFailover false When enabled, all open connections to the previously active database are forcefully closed on a switch, so in-flight operations fail immediately instead of waiting for their socket timeouts.
Retry on failover retryOnFailover false When enabled, a command that fails on a database that has just been switched away from is re-executed on the new active database instead of surfacing the failure to the caller.
Max number of failover attempts maxNumFailoverAttempts 10 How many times Jedis reports the "no healthy database" condition as temporary before it is treated as permanent. Automatic recovery is not guaranteed after that. See When no database is available.
Delay in between failover attempts delayInBetweenFailoverAttempts 12000 ms Minimum time between two counted failover attempts while no healthy database is available.
Initialization policy initializationPolicy MAJORITY_AVAILABLE Decides when MultiDbClient.builder().build() may return based on the initial health check results. See Initialization policy.

Initialization policy

Introduced in version 7.3.0

When a MultiDbClient is created, health checks are started for every database that has them enabled,
and the client waits for their first results before selecting the initial active database.
The InitializationPolicy decides how many databases must be reachable before the client is considered ready,
and when initialization should fail instead. The built-in policies live in InitializationPolicy.BuiltIn:

Policy Client is ready when... Initialization fails when...
ALL_AVAILABLE every database passed its initial health check any database fails its initial health check
MAJORITY_AVAILABLE more than half of the databases are healthy a majority can no longer be reached (default)
ONE_AVAILABLE at least one database is healthy every database failed its initial health check

Databases with health checks disabled are counted as available.
If the policy fails, build() throws a JedisConnectionException.

java
MultiDbConfig config = MultiDbConfig.builder()
        .database(east, 1.0f, clientConfig)
        .database(west, 0.5f, clientConfig)
        .initializationPolicy(InitializationPolicy.BuiltIn.ONE_AVAILABLE) // start as soon as one database is reachable
        .build();

You can also implement InitializationPolicy yourself. The evaluate method receives the number of available,
failed and pending databases and returns CONTINUE, SUCCESS or FAIL.

When no database is available

If a failover is triggered but no other healthy database exists, the command fails with one of two
JedisConnectionException subclasses from the redis.clients.jedis.mcf package:

  • JedisFailoverException.JedisTemporarilyNotAvailableException while the number of counted failover attempts is
    at or below maxNumFailoverAttempts. Attempts are counted at most once per delayInBetweenFailoverAttempts,
    so with the defaults the condition is reported as temporary for roughly two minutes.
  • JedisFailoverException.JedisPermanentlyNotAvailableException once that limit has been exceeded.

The attempt counter resets as soon as a switch to a healthy database succeeds.

While the failure is temporary

Jedis keeps trying and recovers on its own. Applications can catch the temporary variant to back off and retry.

Once the failure is permanent

Jedis gives no guarantee of automatic recovery after JedisPermanentlyNotAvailableException. The client may resume
if a database comes back, but you must not rely on it. User action is required. For example, alert on the exception and
check the databases and the network. Recreating the client (create a new one and close the old one) is the recommended
way to recover.

Health Check Configuration and Customization

The MultiDbClient includes a comprehensive health check system that continuously monitors the availability of Redis databases to enable automatic failover and failback.

The health check system serves several critical purposes in the failover architecture:

  1. Proactive Monitoring: Continuously monitors all databases, including passive ones that aren't currently receiving traffic
  2. Failback Detection: Determines when a previously failed database has recovered and is ready to accept traffic
  3. Circuit Breaker Integration: Works with the circuit breaker pattern to manage database state transitions
  4. Customizable Strategies: Supports pluggable health check implementations for different deployment scenarios

The health check system operates independently of your application traffic, running background checks at configurable intervals to assess database health without impacting performance.

How a health check run works

Every HealthCheckStrategy exposes a small set of parameters that drive the background check:

Parameter Accessor HealthCheckStrategy.Config default Description
Interval getInterval() 5000 ms Time between two health check runs
Timeout getTimeout() 1000 ms Maximum time a single probe may take; a timed out probe counts as a failure
Number of probes getNumProbes() 3 How many times doHealthCheck is invoked (at most) within one run
Delay in between probes getDelayInBetweenProbes() 500 ms Pause between two probes of the same run
Probing policy getPolicy() ProbingPolicy.BuiltIn.ALL_SUCCESS Decides how the individual probe results are combined into one HEALTHY / UNHEALTHY result

The built-in probing policies in ProbingPolicy.BuiltIn are:

  • ALL_SUCCESS — every probe must succeed; the run stops at the first failed probe.
  • ANY_SUCCESS — one successful probe is enough; the run stops at the first successful probe.
  • MAJORITY_SUCCESS — more than half of the probes must succeed; the run stops as soon as the outcome is decided.

The result of a run becomes the database's health status. When the active database turns unhealthy, Jedis puts it into
the grace period and switches to the healthy database with the highest weight.

Available Health Check Types

1. PingStrategy (Default)

The PingStrategy is the default health check implementation. It keeps a small dedicated connection pool to the database
and sends the Redis PING command to verify connectivity and that the server is responding.

Use Cases:

  • General-purpose health checking for most Redis deployments
  • Simple connectivity validation

How it works:

  • Sends PING command to the Redis server using the database's JedisClientConfig (so the same credentials, TLS settings, and timeouts apply)
  • Expects exact response "PONG" to consider the probe successful
  • Any exception, timeout, or unexpected response marks the probe as failed
  • Probe results are combined according to the HealthCheckStrategy.Config shown above (defaults: 3 probes, all must succeed)

PingStrategy.DEFAULT is used when no strategy supplier is configured. To tune its interval, timeout, or probing behavior,
pass a HealthCheckStrategy.Config through your own supplier:

java
HealthCheckStrategy.Config pingConfig = HealthCheckStrategy.Config.builder()
        .interval(2000)                                   // Check every 2 seconds
        .timeout(500)                                     // 500ms timeout per probe
        .numProbes(2)
        .policy(ProbingPolicy.BuiltIn.ANY_SUCCESS)
        .build();

MultiDbConfig.StrategySupplier pingSupplier =
        (hostAndPort, jedisClientConfig) -> new PingStrategy(hostAndPort, jedisClientConfig, pingConfig);

MultiDbConfig.DatabaseConfig dbConfig =
        MultiDbConfig.DatabaseConfig.builder(east, config)
                .healthCheckStrategySupplier(pingSupplier)
                .build();

2. LagAwareStrategy [PREVIEW] (Redis Enterprise)

The LagAwareStrategy is designed specifically for Redis Enterprise Active-Active deployments and uses the Redis Enterprise REST API to check database availability and replication lag.

Use Cases:

  • Redis Enterprise Active-Active (CRDB) deployments
  • Scenarios where replication lag tolerance is critical
  • Enterprise environments with REST API access

How it works:

  • Queries Redis Enterprise REST API for database availability
  • Optionally validates replication lag against a configurable tolerance (extendedCheckEnabled, default true; availabilityLagTolerance, default 5 seconds)
  • Automatically discovers the database ID by matching the database endpoint's hostname against the databases exposed by the REST API

Example Configuration:

java
// REST API endpoint and credentials of the Redis Enterprise cluster
HostAndPort restEndpoint = new HostAndPort("redis-enterprise-cluster-fqdn", 9443);
Supplier<RedisCredentials> restCredentials = () ->
        new DefaultRedisCredentials("rest-api-user", "pwd");

LagAwareStrategy.Config lagConfig = LagAwareStrategy.Config.builder(restEndpoint, restCredentials)
        .interval(5000)                                  // Check every 5 seconds
        .timeout(3000)                                   // 3 second timeout per REST call
        .extendedCheckEnabled(true)                      // Also validate replication lag
        .availabilityLagTolerance(Duration.ofSeconds(2)) // Tolerate up to 2 seconds of lag
        // .sslOptions(sslOptions)                       // Optional: custom truststore for the HTTPS REST API
        .build();

MultiDbConfig.StrategySupplier lagAwareSupplier =
        (hostAndPort, jedisClientConfig) -> new LagAwareStrategy(lagConfig);

MultiDbConfig.DatabaseConfig dbConfig =
        MultiDbConfig.DatabaseConfig.builder(east, config)
                .healthCheckStrategySupplier(lagAwareSupplier)
                .build();

LagAwareStrategy.Config also offers the shortcuts databaseAvailability(...) (availability only, no lag check),
lagAware(...) (default tolerance) and lagAwareWithTolerance(...) for the common configurations.

3. Custom Health Check Strategies

You can implement custom health check strategies by implementing the HealthCheckStrategy interface.

Use Cases:

  • Application-specific health validation logic
  • Integration with external monitoring systems
  • Custom performance or latency-based health checks

Use the healthCheckStrategySupplier() method to provide a custom health check implementation.
The supplier receives the database's HostAndPort and JedisClientConfig, so one supplier can serve several databases:

java
// Custom strategy supplier
MultiDbConfig.StrategySupplier customStrategy =
        (hostAndPort, jedisClientConfig) -> {
            // Return your custom HealthCheckStrategy implementation
            return new MyCustomHealthCheckStrategy(hostAndPort, jedisClientConfig);
        };

MultiDbConfig.DatabaseConfig dbConfig =
        MultiDbConfig.DatabaseConfig.builder(east, config)
                .healthCheckStrategySupplier(customStrategy)
                .weight(1.0f)
                .build();

If you already have a configured strategy instance, healthCheckStrategy(HealthCheckStrategy) wraps it in a supplier for you.
The same instance is then reused for every database that shares that DatabaseConfig, so make sure it is thread-safe.

A minimal HealthCheckStrategy looks like this. Note that doHealthCheck receives the Endpoint being checked,
and that close() is called when the database is removed or the client is closed, so resources should be created once
and released there rather than per probe:

java
public class MyCustomHealthCheckStrategy implements HealthCheckStrategy {

    private final RedisClient client;

    public MyCustomHealthCheckStrategy(HostAndPort hostAndPort, JedisClientConfig jedisClientConfig) {
        this.client = RedisClient.builder().hostAndPort(hostAndPort).clientConfig(jedisClientConfig).build();
    }

    @Override
    public int getInterval() {
        return 1000; // Check every second
    }

    @Override
    public int getTimeout() {
        return 500; // 500ms timeout per probe
    }

    @Override
    public int getNumProbes() {
        return 1;
    }

    @Override
    public ProbingPolicy getPolicy() {
        return ProbingPolicy.BuiltIn.ANY_SUCCESS;
    }

    @Override
    public int getDelayInBetweenProbes() {
        return 100;
    }

    @Override
    public HealthStatus doHealthCheck(Endpoint endpoint) {
        try {
            String result = client.ping();
            return "PONG".equals(result) ? HealthStatus.HEALTHY : HealthStatus.UNHEALTHY;
        } catch (Exception e) {
            return HealthStatus.UNHEALTHY;
        }
    }

    @Override
    public void close() {
        client.close();
    }
}

Disabling Health Checks

Use the healthCheckEnabled(false) method to completely disable health checks for a database:

java
MultiDbConfig.DatabaseConfig dbConfig = MultiDbConfig.DatabaseConfig.builder(east, config)
    .healthCheckEnabled(false) // Disable health checks entirely
    .build();

A database without health checks is always assumed healthy, so it is only taken out of rotation by its circuit breaker
(for the duration of the grace period). Failures are then detected exclusively through the commands your application executes,
and the database is considered available again as soon as the grace period ends.

Fallback configuration

Fallback is what makes a command that hits an open circuit breaker, or that fails while a failover is in progress,
switch to the next database and run there instead of surfacing the error.
Jedis uses the following fallback settings:

Setting Builder method Default value Description
Fallback exception list fallbackExceptionList [CallNotPermittedException, ConnectionFailoverException] A list of Throwable classes that trigger fallback.

CallNotPermittedException is thrown by resilience4j when the circuit breaker is open.
ConnectionFailoverException wraps a command failure that happened on a database which is no longer active,
and is only produced when retryOnFailover is enabled.

Failover callbacks

In the event that Jedis fails over, you may wish to take some action. This might include logging a warning, recording
a metric, or externally persisting the database connection state, to name just a few examples. For this reason,
MultiDbClient lets you register a custom callback that will be called whenever Jedis
switches to a new database.

To use this feature, you'll need to design a class that implements java.util.function.Consumer<DatabaseSwitchEvent>.
This class must implement the accept method, as you can see below.

java
public class FailoverReporter implements Consumer<DatabaseSwitchEvent> {
    
    @Override
    public void accept(DatabaseSwitchEvent e) {
        System.out.println("Jedis switched to database: " + e.getDatabaseName() + " due to " + e.getReason());
    }
}

The DatabaseSwitchEvent consumer is registered on the client builder, next to the MultiDbConfig:

java
FailoverReporter reporter = new FailoverReporter();
MultiDbClient client = MultiDbClient.builder()
        .multiDbConfig(multiConfig)
        .databaseSwitchListener(reporter)
        .build();

The client will call your accept whenever the active database changes.
You can also use a lambda expression:

java
MultiDbClient client = MultiDbClient.builder()
        .multiDbConfig(multiConfig)
        .databaseSwitchListener(event -> System.out.println("Switched to: " + event.getEndpoint()))
        .build();

DatabaseSwitchEvent.getReason() returns a SwitchReason telling you why the switch happened:

SwitchReason Triggered by
CIRCUIT_BREAKER The active database's circuit breaker tripped
HEALTH_CHECK The active database is unhealthy
FAILBACK The periodic failback check found a healthy database with a higher weight
FORCED setActiveDatabase(), forceActiveDatabase(), or removal of the active database via removeDatabase()

Failing back

Jedis supports automatic failback based on health checks or manual failback using the database selection API.

Failback scenario

When a failover is triggered, Jedis will attempt to connect to the next Redis server based on the weights of server configurations
you provide at setup.

For example, recall the redis-east and redis-west deployments from the basic usage example above.
Jedis will attempt to connect to redis-east first.
If redis-east becomes unavailable (and the circuit breaker transitions), then Jedis will attempt to use redis-west.

Now suppose that redis-east eventually comes back online.
You will likely want to fail your application back to redis-east.

Automatic failback based on health checks

When health checks are enabled, Jedis automatically monitors the health of all configured databases, including those that are currently inactive due to previous failures.
The automatic failback process works as follows:

  1. Continuous Monitoring: Health checks run continuously for all databases, regardless of their current active status
  2. Recovery Detection: When a health check run on a previously failed database succeeds (according to the strategy's probing policy), the database is marked as healthy
  3. Grace Period Respect: A database is not eligible for failback until the configured grace period (gracePeriod, default 1 minute) has elapsed since it was marked unhealthy
  4. Weight-Based Failback: Every failbackCheckInterval (default 2 minutes), if failbackSupported is enabled and a healthy, eligible database has a higher weight than the currently active database, Jedis switches to it and emits a FAILBACK switch event

With the default settings, failback can therefore take up to a few minutes after the database recovers.
Lower failbackCheckInterval and gracePeriod if you need a faster failback, keeping in mind that a short grace period
increases the risk of oscillating between databases during intermittent failures.

Manual Failback using the database selection API

Once you've determined that it's safe to fail back to a previously-unavailable database,
you need to decide how to trigger the failback. There are two ways to accomplish this:

setActiveDatabase(endpoint) switches to the given database right away.
Jedis first validates the target by opening a connection and sending PING; if that fails, a JedisValidationException
is thrown and the active database is left unchanged. On success a FORCED switch event is emitted.

java
Endpoint endpoint = new HostAndPort("redis-east.example.com", 14000);
client.setActiveDatabase(endpoint);

Note that automatic failback keeps running: if a healthy database with a higher weight exists, the next periodic failback
check may switch away from the database you selected. Use forceActiveDatabase if you want the choice to stick.

forceActiveDatabase(endpoint, forcedActiveDurationMs) switches to the given database and puts every other database
into a grace period for the given duration, so neither failover nor failback will move traffic away from it during that time.
Because the other databases are in their grace period, commands fail with the exceptions described in
When no database is available if the pinned database itself goes down.
The target must be healthy, otherwise a JedisValidationException is thrown.

java
// Pin redis-east as the active database for the next 10 minutes
client.forceActiveDatabase(endpoint, Duration.ofMinutes(10).toMillis());

Both methods are thread-safe.

If you decide to implement manual failback, you will need a way for external systems to trigger this method in your
application. For example, if your application exposes a REST API, you might consider creating a REST endpoint
to call setActiveDatabase and fail back the application.

Dynamic Weight Management

Introduced in version 7.4.0

Jedis allows you to dynamically adjust database weights at runtime without recreating the MultiDbClient.

Important: Weight determines the priority for selecting which database becomes the active database. At any given time, only ONE database is active and receives all traffic. Weight does not distribute load across databases - it determines which single database Jedis will prefer to use as the active connection.

This is useful for scenarios where you need to change the active database selection priority based on operational conditions, such as:

  • Changing which database should be preferred during planned maintenance
  • Adjusting selection priority based on database performance or regional preferences
  • Implementing controlled switchover between databases
  • Responding to changing infrastructure conditions

Getting and Setting Weights

The MultiDbClient provides methods to query and modify database weights at runtime:

java
// Get the current weight of a database
HostAndPort east = new HostAndPort("redis-east.example.com", 14000);
float currentWeight = client.getWeight(east);
System.out.println("Current weight: " + currentWeight);

// Set a new weight for a database
client.setWeight(east, 2.0f);

Weight Constraints

When setting weights dynamically, the following constraints apply:

  • Weight must be greater than 0: Attempting to set a weight of 0 or negative values will throw an IllegalArgumentException
  • Endpoint must exist: The endpoint must be part of the configured databases, otherwise a JedisValidationException is thrown

Runtime Behavior

When you change a database's weight at runtime:

  1. Immediate Effect on Selection: The weight change takes effect immediately for future active database selection decisions during failover or failback
  2. Automatic Failback Trigger: If automatic failback is enabled and you increase a database's weight above the currently active database, Jedis will automatically switch to the higher-weight database during the next periodic failback check (if the database is healthy and the grace period has elapsed)
  3. No Disruption: Changing weights does not interrupt ongoing operations or force an immediate switch
  4. Single Active Database: Remember that only one database is active at any time - all traffic goes to that single database

How Weight Affects Database Selection

During failover or failback, Jedis selects the active database by:

  1. Filtering for healthy databases (passing health checks, not in grace period, circuit breaker not open)
  2. Sorting the healthy databases by weight in descending order (highest weight first)
  3. Selecting the first database from this sorted list as the active database

Example: Changing Active Database Priority

Here's a practical example of dynamically adjusting weights to control which database should be active:

java
// Initial configuration - primary has higher weight, so it will be selected as active
HostAndPort primary = new HostAndPort("redis-primary.example.com", 6379);
HostAndPort secondary = new HostAndPort("redis-secondary.example.com", 6379);

MultiDbConfig config = MultiDbConfig.builder()
        .database(DatabaseConfig.builder(primary, clientConfig).weight(2.0f).build())
        .database(DatabaseConfig.builder(secondary, clientConfig).weight(1.0f).build())
        .failbackSupported(true)
        .failbackCheckInterval(1000)
        .build();

MultiDbClient client = MultiDbClient.builder()
        .multiDbConfig(config)
        .build();

// At this point, 'primary' is the active database (weight 2.0 > 1.0)

// Before planned maintenance on primary, make secondary the preferred database
client.setWeight(secondary, 3.0f);  // Now secondary has highest weight
// During the next failback check, Jedis will switch to secondary as the active database

// After maintenance, restore primary as the preferred database
client.setWeight(primary, 4.0f);  // Now primary has highest weight again
// During the next failback check, Jedis will switch back to primary as the active database

Monitoring Database Switches

You can combine weight changes with failover callbacks to monitor when the active database switches due to weight adjustments:

java
MultiDbClient client = MultiDbClient.builder()
        .multiDbConfig(config)
        .databaseSwitchListener(event -> {
            System.out.println("Active database switched to: " + event.getEndpoint() +
                             " due to: " + event.getReason());
        })
        .build();

// Change weight - may trigger automatic failback to switch active database
client.setWeight(secondary, 5.0f);

Dynamic Database Management

Jedis allows you to dynamically add and remove database endpoints at runtime without recreating the MultiDbClient. This provides flexibility for scenarios such as:

  • Adding new database replicas or regions as they become available
  • Removing databases during planned maintenance or decommissioning
  • Scaling your Redis infrastructure dynamically
  • Responding to infrastructure changes without application restarts

Adding Databases at Runtime

The MultiDbClient provides two overloaded methods for adding databases dynamically:

Method 1: Using DatabaseConfig

This method provides maximum flexibility for advanced configurations including custom health check strategies, connection pool settings, and other database-specific options.

java
// Create a fully configured DatabaseConfig
HostAndPort newEndpoint = new HostAndPort("redis-new.example.com", 6379);
JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()
        .user("cache").password("secret").build();

DatabaseConfig databaseConfig = DatabaseConfig.builder(newEndpoint, clientConfig)
        .weight(1.5f).connectionPoolConfig(poolConfig).healthCheckEnabled(true).build();

// Add the database to the client
client.addDatabase(databaseConfig);

Method 2: Using Endpoint, Weight, and ClientConfig

This is a convenience method for simpler configurations when you don't need advanced customization.

java
HostAndPort newEndpoint = new HostAndPort("redis-new.example.com", 6379);
JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()
        .user("cache").password("secret").build();

// Add the database with basic configuration
client.addDatabase(newEndpoint, 1.5f, clientConfig);

Removing Databases at Runtime

You can remove database endpoints dynamically using the removeDatabase() method:

java
HostAndPort endpointToRemove = new HostAndPort("redis-old.example.com", 6379);

// Remove the database from the client
client.removeDatabase(endpointToRemove);

Behavior and Constraints

When adding or removing databases, the following behavior applies:

Adding Databases

  • Immediate Availability: The new endpoint becomes available for failover operations immediately after being added and, if health checks are enabled, after its first health check run succeeds
  • Health Check Integration: If health checks are configured, the new database will be monitored according to the configured health check strategy
  • Duplicate Prevention: Attempting to add an endpoint that already exists will throw a JedisValidationException
  • Weight-Based Selection: The new database participates in weight-based active database selection according to its configured weight. Adding a database does not switch the active database by itself; if the new database has the highest weight, the next periodic failback check will switch to it

Removing Databases

  • Automatic Failover: If the removed endpoint is currently the active database, Jedis will automatically switch to the healthy endpoint with the highest weight and emit a FORCED switch event. If no other healthy endpoint exists, the removal is rejected with a JedisException
  • Last Database Protection: You cannot remove the last remaining endpoint - attempting to do so will throw a JedisValidationException
  • Non-Existent Endpoint: Attempting to remove an endpoint that doesn't exist will throw a JedisValidationException
  • Resource Cleanup: The removed database's connections and resources are properly closed and cleaned up
  • Health Check Cleanup: Health checks for the removed database are automatically stopped and unregistered

Querying Configured Databases

You can retrieve the set of all currently configured database endpoints, inspect their health, and find out which one is active:

java
Set<Endpoint> endpoints = client.getDatabaseEndpoints();
System.out.println("Configured databases: " + endpoints);

Endpoint active = client.getActiveDatabaseEndpoint();
System.out.println("Active database: " + active);

for (Endpoint endpoint : endpoints) {
    System.out.println(endpoint + " healthy: " + client.isHealthy(endpoint));
}

isHealthy(endpoint) reflects the same view Jedis uses for failover decisions: the database's health check status,
whether its circuit breaker is open, and whether it is currently in a grace period.

Complete Example: Dynamic Database Management

Here's a practical example demonstrating dynamic database management:

text
/* Detailed source-code truncated for AI context efficiency. */

Thread Safety

Both addDatabase() and removeDatabase() methods are thread-safe and can be called concurrently from multiple threads. The client ensures that database additions and removals are properly synchronized with ongoing operations.

Troubleshooting Failover and Failback Issues

Health Checks Always Report Unhealthy

Common causes:

  • Timeout too aggressive for network conditions
  • Authentication issues with Redis server (the health check uses the database's JedisClientConfig)
  • Network connectivity problems

Solutions:

java
// Increase timeout values
HealthCheckStrategy.Config hcConfig = HealthCheckStrategy.Config.builder()
    .timeout(3000)  // Increase from default 1000ms
    .build();

DatabaseConfig dbConfig = DatabaseConfig.builder(east, config)
    .healthCheckStrategySupplier((hostAndPort, clientConfig) -> new PingStrategy(hostAndPort, clientConfig, hcConfig))
    .build();

Intermittent Health Check Failures

Solutions:

java
// Tolerate a single failed probe instead of requiring all probes to succeed
HealthCheckStrategy.Config hcConfig = HealthCheckStrategy.Config.builder()
    .interval(5000)                                  // Less frequent checks
    .timeout(2000)                                   // More generous timeout
    .numProbes(3)
    .policy(ProbingPolicy.BuiltIn.MAJORITY_SUCCESS)  // 2 out of 3 probes must succeed
    .build();

Slow Failback After Recovery

With the defaults, a recovered database is picked up again only after its 1 minute grace period has passed
and the next failback check runs, which happens every 2 minutes.

Solutions:

java
// Faster recovery detection
HealthCheckStrategy.Config hcConfig = HealthCheckStrategy.Config.builder()
    .interval(1000)                    // More frequent checks
    .build();

// Adjust failback timing
MultiDbConfig multiConfig = MultiDbConfig.builder()
        .database(east, 1.0f, config)
        .database(west, 0.5f, config)
        .failbackCheckInterval(5000)       // Look for a better database every 5 seconds
        .gracePeriod(10000)                // Shorter grace period
        .build();

JedisPermanentlyNotAvailableException

Cause: no database was usable for longer than the configured tolerance (maxNumFailoverAttempts counted attempts,
at most one every delayInBetweenFailoverAttempts).

Solutions:

  • Jedis gives no guarantee of automatic recovery after this exception. Recreating the client (create a new one and close
    the old one) is the recommended way to recover. Checking the databases and the network is a good first step.
  • To ride out longer outages without recreating the client, increase maxNumFailoverAttempts and
    delayInBetweenFailoverAttempts. See When no database is available.

Need help or have questions?

For assistance with this automatic failover and failback feature,
start a discussion.


Faq (`docs/faq.md`)

Frequently Asked Questions

If you get `java.net.SocketTimeoutException: Read timed out` exception

Try setting own timeout value when constructing JedisPool using the following constructor:

java
JedisPool(GenericObjectPoolConfig poolConfig, String host, int port, int timeout)

where timeout is given as milliseconds.

Default timeout value is 2 seconds.

JedisPool blocks after getting 8 connections

JedisPool defaults to 8 connections, you can change this in the PoolConfig:

java
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(maxTotal); // maximum active connections
poolConfig.setMaxIdle(maxIdle);  // maximum idle connections

Take into account that JedisPool inherits commons-pool BaseObjectPoolConfig which has a lot of configuration parameters.
We've set some defined ones which suit most of the cases. In case, you experience issues tuning these parameters may help.

How to configure the buffer size of socket(s)

The buffer size of all Jedis sockets in an application can be configured through system property.

Buffer size of input stream can be configured by setting jedis.bufferSize.input or jedis.bufferSize system property.
Buffer size of output stream can be configured by setting jedis.bufferSize.output or jedis.bufferSize system property.
If you want to set the buffer size of both input and output stream to same value, you can just set jedis.bufferSize.

Note: This feature is available since Jedis 4.2.0.

How to avoid cluster initialization error

As of Jedis 4.0.0, a JedisClusterOperationException is raised with the message Could not initialize cluster slots cache. when the cluster initialization process fails.

Should you would want to avoid this error (for example, creating JedisConnectionFactory to an unavailable cluster for a spring-data-redis Bean), set the system property jedis.cluster.initNoError to any value.
In the console, add the option -Djedis.cluster.initNoError.
In an application, System.setProperty("jedis.cluster.initNoError", ""); can be set before creating any cluster object.

Note: This feature is available since Jedis 4.4.2.


Hash Import (`docs/hash-import.md`)

Hash Import (HIMPORT)

Overview

The HIMPORT command (Redis 8.10 and later) is a fast way to create many hashes
that share a common set of field names — for example, importing a batch of records where every
record has the same columns. Rather than sending the field names again for every hash (as a
series of HSET calls would), the field names are declared once per connection and each hash
then supplies only its values, positionally matched to those fields.

On the wire this is a connection-local container command: HIMPORT PREPARE registers a named
field-set (the ordered field names) on the current connection, HIMPORT SET creates one hash
from a row of values against that field-set, and HIMPORT DISCARD releases it. A field-set
lives only on the connection that prepared it and disappears when that connection is reset or
closed.

Depending on the client, commands travel over a single direct connection (Jedis) or over a
pool of connections (RedisClient, RedisClusterClient) — with a pool, each himportSet can
end up on an arbitrary connection. Rather than expose the raw connection-local commands — which
are not safe to drive by hand across pooled connections — Jedis exposes a reusable HashImport
field-set template plus a per-row himportSet command, and manages the lifecycle: the
HIMPORT PREPARE is injected automatically the first time a template is seen on each
connection, a redirect to another cluster node simply re-prepares on demand, and closing the
template schedules HIMPORT DISCARD on every connection it touched. You never manage
PREPARE/SET/DISCARD ordering or connection pinning yourself.

HashImport and himportSet are marked @Experimental: the API may change in a future
release. They require Redis 8.10 or later.

Usage

Create a field-set template once (declaring the shared field names, in order), then import each
hash by supplying its key and its values positionally against those fields:

java
import redis.clients.jedis.HashImport;
import redis.clients.jedis.RedisClient;

RedisClient client = RedisClient.create("redis://localhost:6379");

// declare the field names shared by every hash we are importing; reusable and safe to share
try (HashImport fields = HashImport.of("name", "email", "age")) {

    // import as many hashes as you like — one command per hash, so the total is unbounded
    client.himportSet("user:1", fields, "alice", "[email protected]", "30");
    client.himportSet("user:2", fields, "bob",   "[email protected]", "25");
    client.himportSet("user:3", fields, "carol", "[email protected]", "42");
}

After these complete, user:1, user:2 and user:3 each exist as an ordinary hash with the
name, email and age fields set to their respective values — readable with HGETALL,
HGET and every other hash command. Any existing hash at an imported key is replaced.

Closing the template (try-with-resources or close()) schedules a best-effort
HIMPORT DISCARD to release the server-side state; it is optional (the state also dies with
the connection) but good hygiene for long-lived connections. A closed template must not be used
again.

Pipelining

For large imports, combine the template with a pipeline to avoid a round trip per hash. The
HIMPORT PREPARE is buffered automatically ahead of the first use on the pipeline's
connection, and its reply is internal — syncAndReturnAll() returns exactly one result per
command you issued:

java
import redis.clients.jedis.HashImport;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.RedisClient;
import redis.clients.jedis.Response;

RedisClient client = RedisClient.create("redis://localhost:6379");

try (HashImport fields = HashImport.of("name", "email", "age");
    Pipeline pipeline = client.pipelined()) {

    Response<String> u1 = pipeline.himportSet("user:1", fields, "alice", "[email protected]", "30");
    Response<String> u2 = pipeline.himportSet("user:2", fields, "bob",   "[email protected]", "25");
    Response<String> u3 = pipeline.himportSet("user:3", fields, "carol", "[email protected]", "42");

    pipeline.sync();

    // each returns "OK"
    u1.get();
    u2.get();
    u3.get();
}

Cluster

himportSet works unchanged with RedisClusterClient: each key routes to its slot, and the
template re-prepares per node as needed.

Limitations

  • Transactions: himportSet is not supported inside MULTI/EXEC — the connection-local
    PREPARE cannot be staged in a transaction. Attempting it throws
    UnsupportedOperationException.
  • Cluster pipelines: not supported. Use the standard (non-pipelined) himportSet on
    RedisClusterClient instead.
  • A template dropped without close() leaves its server-side state on the connections it
    touched until those connections are recycled.

Integration Testing (`docs/integration-testing.md`)

Integration Testing Infrastructure

This document describes how Jedis integration tests are wired up: the Redis test
environment, how it is started, how tests discover their servers, and how this
maps to CI.

Tips & tricks

Tests do not start Redis. They connect to servers that are already running. The
servers are defined by src/test/resources/env/docker-compose.yml, started via
make start (Makefile), and described to the tests by
src/test/resources/endpoints.json. A test selects a server by name through
Endpoints.getRedisEndpoint("<name>")
(src/test/java/redis/clients/jedis/Endpoints.java).

  • Run integration tests locally: make start version=8.6 && mvn clean verify && make stop.
  • Run one test fast: make start version=8.6 then mvn -Dtest=YourIT verify
    (don't forget make stop).
  • Add a new server: add a service to src/test/resources/env/docker-compose.yml
    (set REDIS_CLUSTER/TLS_ENABLED/TLS_CLIENT_CNS/ports/volumes), then add a
    matching named entry to src/test/resources/endpoints.json.
  • Reference it in a test: Endpoints.getRedisEndpoint("<name>")
    (src/test/java/redis/clients/jedis/Endpoints.java) →
    endpoint.getClientConfigBuilder().build().
  • TLS in a test: build a truststore from endpoint.getCertificatesLocation()
    via TlsUtil (src/test/java/redis/clients/jedis/util/TlsUtil.java); certs
    come from <TEST_WORK_FOLDER>/<env>/work/tls/.
  • A test got skipped, not run: its endpoint name is missing from the active
    endpoints file (src/test/resources/endpoints.json for oss-docker,
    src/test/resources/endpoints_source.json for oss-source) — you're probably
    on the oss-source (local) provider.
  • Reproducing CI: the test build pins Java 8.

1. The test environment is provided by a shared container

All Redis servers used in tests run inside the redislabs/client-libs-test
Docker image, published publicly on
Docker Hub. The same image
is used across several Redis client libraries (Jedis, Lettuce, redis-py,
node-redis, go-redis, …).

The image wraps a chosen Redis version and bootstraps a standalone,
replication + sentinel, or cluster topology — optionally with
TLS / mTLS and modules — entirely from environment variables. Tags follow the
Redis version (e.g. redislabs/client-libs-test:8.6). It is not for
production use.

You normally never run it by hand — make start brings up the whole topology via
Docker Compose (see §4). But the image is self-contained, so you can launch a
single instance directly to experiment:

bash
# standalone on :6379
docker run --rm -p 6379:6379 redislabs/client-libs-test:8.6
# 3-node cluster on :7000-7002
docker run --rm -p 7000-7002:7000-7002 \
  -e REDIS_CLUSTER=yes -e NODES=3 -e PORT=7000 redislabs/client-libs-test:8.6

1.1 Container behaviour

Concern How it works
Base image redis:<tag> (OSS) or redis/redis-stack:<tag> (with modules), selectable at build time via BASE_IMAGE / BASE_IMAGE_TAG.
Topology REDIS_CLUSTER=yes → cluster (auto-bootstrapped with redis-cli --cluster create); otherwise standalone/replicated. NODES=N sets node count (cluster default 3, standalone 1). REPLICAS=N sets replicas per cluster master.
Ports Non-TLS nodes: PORT, PORT+1, … (default PORT=3000). TLS nodes: TLS_PORT, TLS_PORT+1, … (default TLS_PORT=4430).
Auth REDIS_PASSWORD → requirepass+masterauth. REDIS_CLIENT_USER/REDIS_CLIENT_PASSWORD are the credentials the container itself uses to create/check the cluster.
Protected mode PROTECTED_MODE=yes sets Redis protected-mode (default no).
TLS TLS_ENABLED=yes auto-generates a self-signed CA, a server cert, and client certs into /redis/work/tls/, and enables a TLS port per node.
mTLS TLS_CLIENT_CNS="cn1 cn2 …" generates one client cert (+ .p12) per CN. TLS_AUTH_CLIENTS_USER=CN maps the cert CN to a Redis ACL user (Redis ≥ 8.6); off disables client-cert auth.
Modules When the base image ships modules (e.g. redis/redis-stack:<tag>), they are auto-loaded. redislabs/client-libs-test has matching …-stack style tags for module testing.
Extra directives Any trailing args become redis-server flags (e.g. --maxmemory 256mb). A few are container-managed and cannot be overridden: --port, --dir, --logfile, --pidfile, --cluster-enabled, --cluster-config-file, --protected-mode.

1.2 Volumes & directory conventions

Mount Purpose
-v <host>/config:/redis/config:r Input. Pre-baked per-node config. Node dirs must be named node-[-<tlsport>]; a sentinel node dir must be named node-sentinel-<...>. Each may contain redis.conf and (for cluster) nodes.conf. Optional tls/ subdir supplies pre-generated certificates.
-v <host>/work:/redis/work:rw Output. Runtime state: node-/{redis.conf,redis.log,redis.pid,nodes.conf} and generated tls/ (ca.crt, ca.key, redis.crt, redis.key, <cn>.p12, …). Tests read TLS certs from here.

1.3 Generated TLS material

When TLS_ENABLED=yes, the container writes these files to its
/redis/work/tls/ directory (surfaced on the host under the work/ mount — see
§1.2 — which is where the tests read them from):

File What it is
ca.crt, ca.key Self-signed test CA (signs everything else).
redis.crt, redis.key Server certificate (CN localhost).
<cn>.crt, <cn>.key, <cn>.p12 One client cert per name in TLS_CLIENT_CNS, for mTLS.

The PKCS#12 keystores (*.p12) use the password changeit. Certs are
generated once and reused on container restart.

1.4 "Endpoint" definition

An endpoint is a host:port pair for one Redis node (127.0.0.1:6379
non-TLS, 127.0.0.1:6390 TLS). For cluster, every node is an endpoint and the
client discovers the rest via CLUSTER NODES.


2. How Jedis defines its environment

Everything lives under src/test/resources/env/.

text
src/test/resources/env/
├── docker-compose.yml         # all Redis services (standalone/sentinel/cluster/tls/mtls/stack)
├── .env                       # base vars (default REDIS_VERSION, image name, work dir)
├── .env.v6.2 … .env.v8.12     # per-version overrides (just pin REDIS_VERSION)
├── redis1-2-5-8-sentinel/     # one dir per environment
│   ├── config/node-<port>/redis.conf   # mounted read-only into the container
│   └── work/                  # runtime state (TLS certs land in work/tls/)
├── standalone2-sentinel/
├── cluster-unbound/
├── ...

Key .env values:

text
REDIS_VERSION=8.6.1
CLIENT_LIBS_TEST_IMAGE=redislabs/client-libs-test
REDIS_ENV_CONF_DIR=./                       # base for the :/redis/config mounts
REDIS_ENV_WORK_DIR=/tmp/redis-env-work      # base for the :/redis/work mounts

Each docker-compose.yml service is one client-libs-test container, e.g.:

yaml
redis1-2-5-8-sentinel:
  <<: *client-libs-image       # image: ${CLIENT_LIBS_TEST_IMAGE}:${REDIS_VERSION}
  environment: [REDIS_CLUSTER=no, REDIS_CLIENT_USER=deploy,
                REDIS_CLIENT_PASSWORD=verify, TLS_ENABLED=yes]
  ports: ["6379:6379", "6390:6390", "26379:26379", "36379:36379"]
  volumes:
    - ${REDIS_ENV_CONF_DIR}/redis1-2-5-8-sentinel/config:/redis/config:r
    - ${REDIS_ENV_WORK_DIR}/redis1-2-5-8-sentinel/work:/redis/work:rw

The compose file provides the full matrix: standalone (+TLS/+ACL), sentinel,
cluster-unbound, cluster-stable (+TLS), standalone-mtls, cluster-mtls,
a Redis-Stack service, Toxiproxy (network fault injection), and an explicitly
unavailable instance for failover tests.


3. How tests discover servers: `endpoints.json`

Tests never hardcode hosts. They look up a named entry in
src/test/resources/endpoints.json, loaded by Endpoints.getRedisEndpoint(name).

3.1 Schema

jsonc
{
  "standalone0-tls": {
    "endpoints":     ["rediss://localhost:6390"], // one URI (standalone) or many (cluster)
    "tls":           true,                         // required
    "username":      "default",                    // optional ACL user
    "password":      "foobared",                   // optional
    "tls_cert_path": "redis1-2-5-8-sentinel/work/tls", // optional; resolved under TEST_WORK_FOLDER
    "bdbId":         0                             // optional (Redis Enterprise only)
  }
}

redis:// = plaintext, rediss:// = TLS. Names encode the topology and mode,
e.g. standalone0, standalone0-acl, standalone0-tls,
standalone0-tls-wronghost (CN-mismatch negative test), cluster-stable,
cluster-stable-tls, cluster-mtls, sentinel-standalone0.

3.2 The Java side (`src/test/java/redis/clients/jedis/`)

Class Role
Endpoints Loads the JSON at class-init; getRedisEndpoint(name) returns an EndpointConfig (or throws TestAbortedException → test skipped, not failed, when the endpoint is absent).
EndpointConfig Parsed entry (Gson, LOWER_CASE_WITH_UNDERSCORES). Provides getHostAndPort(), getHostsAndPorts(), getPassword(), isTls(), getCertificatesLocation(), and getClientConfigBuilder() (pre-wired credentials + SSL socket factory).
util/TestEnvUtil Chooses which endpoints file to load (see §6).
util/TlsUtil Builds a JCEKS truststore from the tls_cert_path cert dir(s) and sets javax.net.ssl.trustStore* system properties.

Typical usage:

java
@BeforeAll static void setUp() {
  endpoint = Endpoints.getRedisEndpoint("standalone0-tls");
}
// ...
try (Jedis j = new Jedis(endpoint.getHostAndPort(), endpoint.getClientConfigBuilder().build())) { ... }

mTLS / TLS tests build a truststore from the cert location(s):

java
trustStorePath = TlsUtil.createAndSaveTestTruststore(name, List.of(endpoint.getCertificatesLocation()), "changeit");
TlsUtil.setCustomTrustStore(trustStorePath, "changeit");

4. Running the tests (Makefile)

Command Effect
make start version=8.6 docker compose … up -d --wait with .env + .env.v8.6. Supported: 6.2, 7.2, 7.4, 8.0, 8.2, 8.4, 8.6, 8.8, 8.10, 8.12 (milestone 8.12-m02-int; default 8.10).
make stop docker compose … down.
make test [version=…] start → mvn clean verify → stop.
mvn clean verify Run tests against an already-started env (use this from the IDE between make start/make stop).

Custom image instead of a version: make start CLIENT_LIBS_TEST_IMAGE_TAG=<tag>.

Test selection (JUnit 5 tags + Maven, see `pom.xml`)

Group Tag / naming Runner Default
Unit @Tag("unit"), no network Surefire run (skipUnitTests=false)
Integration *IT (new) — legacy *IntegrationTest / @Tag("integration") still run Failsafe run (skipIntegrationTests=false)
Scenario @Tag("scenario") Failsafe skipped (skipScenarioTests=true)

New integration tests must be named *IT — do not use *IntegrationTest or
the @Tag("integration") marker. See §5.

Useful flags: -DskipUnitTests=true (CI Docker job runs integration only),
-Dtest=<Class#method> to narrow.


5. Test source layout & where to put tests

All test code lives under src/test/java/redis/clients/jedis/. The package
root holds the broad client/pool/connection tests; everything else is grouped by
area:

text
src/test/java/redis/clients/jedis/
├── commands/          # command coverage (integration)
│   ├── jedis/         #   against the legacy Jedis API
│   ├── unified/       #   against the UnifiedJedis surface
│   └── commandobjects/
├── search/  json/  timeseries/   # module command tests (integration)
├── tls/               # TLS / mTLS / SSL options (integration, *IT.java)
├── csc/               # client-side caching (integration)
├── failover/          # multi-db / failover (integration, @Tag("failover"))
├── authentication/    # auth + token-based auth
├── mcf/  providers/  executors/  prefix/   # connection/provider internals
├── params/  builders/  resps/              # mostly unit (arg/response objects)
├── mocked/            # pure unit, Mockito-based — no server
├── scenario/          # @Tag("scenario") — long-running, skipped by default
├── examples/          # io.redis.examples — excluded from test runs
├── benchmark/  codegen/                    # excluded from test runs
└── util/              # shared test helpers (TestEnvUtil, TlsUtil, …) — not tests

Unit vs. integration: the rule

What decides whether a test is run by Surefire (unit) or Failsafe (integration)
is how the test is marked — its file-name suffix and/or JUnit tag — not which
folder it sits in (the runners select by suffix and tag — see §4 and pom.xml).

New integration tests MUST be named *IT. This is the single accepted
convention for new code. Failsafe still recognises the older *IntegrationTest
name and the @Tag("integration") marker so the many existing classes keep
running while they are migrated, but do not use them for new tests — name
your class FooIT and nothing else.

Test needs… Name / mark it as… Runner
No server (pure logic, mocks) a plain *Test / *Tests class, untagged Surefire
A running Redis *IT file name Failsafe
A long-running scenario @Tag("scenario"), placed under scenario/ Failsafe (off by default)

Guidance for new tests:

  • Integration test → name the file FooIT.java (the only accepted name
    for new integration tests — not *IntegrationTest, and never
    @Tag("integration")). Put it in the area package it exercises (commands/…,
    search/, tls/, etc.). It connects to a server resolved through
    Endpoints.getRedisEndpoint(...).
  • Unit test → add it next to related unit tests (or under mocked/ if it is
    Mockito-based), name it FooTest, and leave it untagged. It must pass with no
    Redis running.
  • Never put production-required fixtures in examples/, benchmark/, or
    codegen/ — those packages are excluded from the test run. Shared helpers go
    in util/ (helpers there are not collected as tests even if named *Test).

6. Two environments: Docker (full) vs. local-from-source (limited)

Selection is via the TEST_ENV_PROVIDER env var, read by TestEnvUtil:

Provider Endpoints file Servers Use
oss-docker (default) endpoints.json Full compose matrix (standalone, sentinel, cluster, TLS, mTLS, stack) Full integration coverage.
oss-source endpoints_source.json 3 plain redis-server processes started by make start-local: standalone0 (6379, ACL users), a Unix-socket instance, and one deliberately-unavailable instance Lightweight local dev; limited subset.
re (REDIS_ENDPOINTS_CONFIG_PATH) Redis Enterprise Internal.

Override the endpoints file directly with REDIS_ENDPOINTS_CONFIG_PATH, and the
TLS cert root with TEST_WORK_FOLDER (default /tmp/redis-env-work).

Local-from-source flow

bash
make system-setup    # CI only: build Redis from github.com/redis/redis + the test module (testmodule.so)
make test-local      # = start-local (TEST_ENV_PROVIDER=oss-source) → mvn verify → stop-local

make start-local runs three local redis-server instances and make mvn-test-local forces TEST_ENV_PROVIDER=oss-source, so tests load
endpoints_source.json. Endpoints absent there (cluster, sentinel, TLS, mTLS,
stack) resolve to a TestAbortedException and are skipped — that is why the
local env runs only a subset. Full coverage requires the Docker env.


7. CI workflows (`.github/workflows/`)

Workflow Environment What it does
test-on-docker.yml Docker (full) Matrix over Redis 7.2 → 8.12. Uses the run-tests composite action: make start version=<v> → TEST_ENV_PROVIDER=oss-docker, TEST_WORK_FOLDER=$REDIS_ENV_WORK_DIR, mvn -B -DskipUnitTests=true clean compile verify → make stop. Uploads JaCoCo + Surefire/Failsafe reports.
integration.yml Local-from-source (limited) Java 8; make system-setup builds Redis from source + the test module; make test-local runs the oss-source subset.

Both clients pin Java 8 for the test build. Use it when reproducing CI.


Jedis Maven (`docs/jedis-maven.md`)

Use Jedis as a maven dependency:

Official Releases

xml
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>8.0.1</version>
</dependency>

Snapshots

xml
<repositories>
    <repository>
        <id>central-snapshots</id>
        <url>https://central.sonatype.com/repository/maven-snapshots/</url>
        <releases>
            <enabled>false</enabled>
        </releases>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
</repositories>

and

xml
<dependencies>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>8.1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

Redisearch (`docs/redisearch.md`)

RediSearch Jedis Quick Start

To use RediSearch features with Jedis, you'll need to use an implementation of RediSearchCommands.

Creating the RediSearch client

Initializing the client with RedisClient:

java
RedisClient client = RedisClient.builder().hostAndPort("localhost", 6379).build();

Initializing the client with RedisClusterClient:

java
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("127.0.0.1", 7379));
nodes.add(new HostAndPort("127.0.0.1", 7380));

RedisClusterClient client = RedisClusterClient.builder().nodes(nodes).build();

Indexing and querying

Indexing

Defining a schema for an index and creating it:

java
Schema sc = new Schema()
        .addTextField("title", 5.0)
        .addTextField("body", 1.0)
        .addNumericField("price");

IndexDefinition def = new IndexDefinition()
        .setPrefixes(new String[]{"item:", "product:"})
        .setFilter("@price>100");

client.ftCreate("item-index", IndexOptions.defaultOptions().setDefinition(def), sc);

Alternatively, we can create the same index using FTCreateParams:

java
client.ftCreate("item-index",

        FTCreateParams.createParams()
                .prefix("item:", "product:")
                .filter("@price>100"),

        TextField.of("title").weight(5.0),
        TextField.of("body"),
        NumericField.of("price")
);

Inserting

Adding documents to the index:

java
Map<String, Object> fields = new HashMap<>();
fields.put("title", "hello world");
fields.put("state", "NY");
fields.put("body", "lorem ipsum");
fields.put("price", 1337);

client.hset("item:hw", RediSearchUtil.toStringMap(fields));

Another way to insert documents:

java
client.hsetObject("item:hw", fields);

Querying

Searching the index:

java
Query q = new Query("hello world")
        .addFilter(new Query.NumericFilter("price", 0, 1000))
        .limit(0, 5);

SearchResult sr = client.ftSearch("item-index", q);

Alternative searching using FTSearchParams:

java
SearchResult sr = client.ftSearch("item-index",
        "hello world",
        FTSearchParams.searchParams()
                .filter("price", 0, 1000)
                .limit(0, 5));

Aggregation query:

java
AggregationBuilder ab = new AggregationBuilder("hello")
        .apply("@price/1000", "k")
        .groupBy("@state", Reducers.avg("@k").as("avgprice"))
        .filter("@avgprice>=2")
        .sortBy(10, SortedField.asc("@state"));

AggregationResult ar = client.ftAggregate("item-index", ab);

Redisjson (`docs/redisjson.md`)

RedisJSON Jedis Quick Start

Jedis supports RedisJSON and RediSearch.

The latest versions of RedisJSON let you store, manipulate, index, and query JSON.
To use these features with Jedis, you'll need to use the UnifiedJedis interface or a sub-class of it.

Let's see how this works.

Creating with RedisJSON client

First, let's create a RedisClient client instance:

java
RedisClient client = RedisClient.builder().hostAndPort("localhost", 6479).build();

Or, a RedisClusterClient client instance:

java
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("127.0.0.1", 7379));
nodes.add(new HostAndPort("127.0.0.1", 7380));

RedisClusterClient client = RedisClusterClient.builder().nodes(nodes).build();

Now we can start working with JSON. For these examples, we'll be using GSON
to handle the serialization of POJOs to JSON.

Creating JSON documents

Suppose we're building an online learning platform, and we want to represent students.
Let's create a POJO to represent our students:

java
private class Student {
    private String firstName;
    private String lastName;

    public Student(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }
}

Now we can create some students and store them in Redis as JSON:

java
final Gson gson = new Gson();

Student maya = new Student("Maya", "Jayavant");
client.jsonSet("student:111", gson.toJson(maya));

Student oliwia = new Student("Oliwia", "Jagoda");
client.jsonSet("student:112", gson.toJson(oliwia));

Some of other ways to store POJOs as JSON:

text
client.jsonSetLegacy("student:111", maya);
client.jsonSetWithEscape("student:112", oliwia);

Querying and indexing JSON

If we want to be able to query this JSON, we'll need to create an index. Let's create an index on the "firstName" and "lastName" fields.

  1. We define which fields to index ("firstName" and "lastName").
  2. We set up the index definition to recognize JSON and include only those documents whose key starts with "student:".
  3. Then we actually create the index, called "student-index", by calling ftCreate().
java
Schema schema = new Schema().addTextField("$.firstName", 1.0).addTextField("$.lastName", 1.0);

IndexDefinition rule = new IndexDefinition(IndexDefinition.Type.JSON)
        .setPrefixes(new String[]{"student:"});

client.ftCreate("student-index", IndexOptions.defaultOptions().setDefinition(rule), schema);

Alternatively creating the same index using FTCreateParams:

java
client.ftCreate("student-index",
        FTCreateParams.createParams().on(IndexDataType.JSON).prefix("student:"),
        TextField.of("$.firstName"), TextField.of("$.lastName"));

With an index now defined, we can query our JSON. Let's find all students whose name begins with "maya":

java
Query q = new Query("@\\$\\.firstName:maya*");
SearchResult mayaSearch = client.ftSearch("student-index", q);

Same query can be done using FTSearchParams:

java
SearchResult mayaSearch = client.ftSearch("student-index",
        "@\\$\\.firstName:maya*",
        FTSearchParams.searchParams());

We can then iterate over our search results:

java
List<Document> docs = mayaSearch.getDocuments();
for (Document doc : docs) {
   System.out.println(doc);
}

This example just scratches the surface. You can atomically manipulate JSON documents and query them in a variety of ways.
See the RedisJSON docs, the RediSearch docs,
and our course, "Querying, Indexing, and Full-text Search in Redis",
for a lot more examples.


Release Process (`docs/release-process.md`)

Release Process

Releases are drafted automatically by
Release Drafter
and deployed by the
Release workflow
when a draft is published.

Branches

Branch Hosts Example
master next minor or major 8.1.0-SNAPSHOT
MAJOR.x MAJOR.0.PATCH 7.x, 8.x
MAJOR.MINOR.x MAJOR.MINOR.PATCH 7.5.x, 8.1.x
  • Minor and major releases, including betas, are tagged on master.
  • Patch releases are tagged on their maintenance branch, never on master.
  • A maintenance branch is cut from the GA tag commit (8.1.x from v8.1.0).
  • Fixes land on master first and are backported with a PR titled
    Backport # to <line>: <original title>, carrying the original labels.

How the draft is produced

On every push to master or a maintenance branch, Release Drafter updates one
draft per branch:

  • Version is derived from the nearest GA tag reachable from the branch,
    ignoring prerelease tags: patch bump on maintenance branches, minor bump on
    master. Example: 8.x at v8.0.1 drafts 8.0.2; master at v8.0.0
    drafts 8.1.0.
  • Changes are the pull requests merged to the branch since the latest GA
    release published from that branch. For the first patch after a branch is
    cut, the GA tag's commit date is used as the cutoff instead.
  • Betas are prereleases and are not used as a base, so GA notes are cumulative
    since the previous GA.
  • PRs labeled skip-changelog or churn are excluded. Unlabeled PRs appear
    uncategorized, so label backports.

Publishing

  1. Review the draft on the Releases page and edit the notes if needed.
  2. Publish from the draft. This keeps the release target on the right branch,
    which the next draft on that branch depends on. Do not create releases
    from the tag page.
  3. For a patch of an older line, untick Set as the latest release.
  4. Publishing creates the tag and triggers the Release workflow, which sets
    the Maven version from the tag and deploys to Maven Central.

Major releases and betas are not derived automatically. Run the workflow
manually with the version input, for example 9.0.0 or 8.1.0-beta2.

Version bumps

The Release workflow sets the artifact version from the tag, so the pom.xml
version on a maintenance branch does not affect published artifacts. Bump the
snapshot version on master only after a minor or major release, with the PR
labeled skip-changelog.


Requirements (`docs/requirements.txt`)

mkdocs~=1.6
mkdocs-material~=9.5
pymdown-extensions~=10.8
mkdocs-macros-plugin~=1.0
mkdocs-glightbox


Smart Client Handoffs (`docs/smart-client-handoffs.md`)

Smart Client Handoffs (Maintenance Notifications)

Overview

Smart client handoffs (SCH) is a feature of
Redis Cloud and Redis Software servers that notifies clients about planned maintenance — shard
migrations, shard failovers, and endpoint rebinds — shortly before it happens, so the client can
respond gracefully instead of hitting command timeouts and dropped connections. The notifications
arrive as RESP3 push messages, and Jedis reacts on the client side:

  • Relaxed timeouts — while a maintenance operation is in progress, socket read timeouts are
    temporarily loosened so in-flight and new commands survive the operation instead of timing out.
  • Pre-handoffs — when an endpoint moves (MOVING), the pool proactively directs new
    connections to the new endpoint and retires connections to the old one within the announced grace
    period, instead of waiting for a hard disconnect.

The feature is negotiated per connection during the handshake with
CLIENT MAINT_NOTIFICATIONS ON. Servers that do not support the command are unaffected (see
Modes).

Requirements

  • RESP3. Push messages require the RESP3 protocol. On a RESP2 connection the feature stays
    inactive in AUTO mode and fails the connection setup in ENABLED mode.
  • Server support. The server must support the CLIENT MAINT_NOTIFICATIONS command; see
    SCH support in Redis server products
    for availability and enablement per product.
  • Client. RedisClient (standalone) supports the feature and enables it automatically on RESP3
    connections.

Enabling

With RedisClient, using RESP3 is enough — the default mode is AUTO:

java
RedisClient client = RedisClient.builder()
    .hostAndPort(new HostAndPort("localhost", 6379))
    .clientConfig(DefaultJedisClientConfig.builder()
        .protocol(RedisProtocol.RESP3)
        .build())
    .build();

To customize the behavior, or turn the feature off:

java
RedisClient client = RedisClient.builder()
    .hostAndPort(new HostAndPort("localhost", 6379))
    .clientConfig(DefaultJedisClientConfig.builder()
        .protocol(RedisProtocol.RESP3)
        .build())
    .maintenanceNotifications(MaintenanceNotificationsConfig.builder()
        .mode(MaintenanceNotificationsConfig.Mode.ENABLED) // fail setup if unsupported
        .relaxedTimeout(30_000)                            // non-blocking commands, millis
        .relaxedBlockingTimeout(0)                         // blocking commands; 0 = infinite
        .build())
    .build();

// off:
RedisClient plain = RedisClient.builder()
    .hostAndPort(new HostAndPort("localhost", 6379))
    .maintenanceNotifications(MaintenanceNotificationsConfig.DISABLED)
    .build();

Modes

Mode Behavior
AUTO (default for RedisClient) The handshake is attempted on RESP3 connections; if the server rejects it (or the connection is RESP2), the feature is quietly disabled for that connection.
ENABLED The handshake must succeed: connection setup fails if the server rejects CLIENT MAINT_NOTIFICATIONS or the connection is not RESP3.
DISABLED The handshake is not attempted; the feature is off.

Notifications and how the client reacts

Notification Wire format Meaning Client reaction
MIGRATING ["MIGRATING", seq, startsInSeconds, shard-ids] A shard migration starts within the lead time. Relax the receiving connection's timeouts.
MIGRATED ["MIGRATED", seq, shard-ids] The migration completed. Restore the receiving connection's timeouts.
FAILING_OVER ["FAILING_OVER", seq, startsInSeconds, shard-ids] A shard failover starts within the lead time. Relax the receiving connection's timeouts.
FAILED_OVER ["FAILED_OVER", seq, shard-ids] The failover completed. Restore the receiving connection's timeouts.
MOVING ["MOVING", seq, graceSeconds, "host:port"] The endpoint moves to the target; connections to the old endpoint hard-disconnect after the grace period. Relax timeouts pool-wide, direct new connections to the target, and retire connections to the old endpoint (see below).

Relaxed timeouts

While a maintenance window is open, the connection uses the looser of the relaxed value and the
configured socket timeout (0 = infinite is the loosest) — relaxation never tightens a timeout.
Non-blocking and blocking commands have separate relaxed values (relaxedTimeout, default 10 s, and
relaxedBlockingTimeout, default 0 = infinite).

A window opened by MIGRATING/FAILING_OVER normally closes with the matching
MIGRATED/FAILED_OVER. If the closing notification is lost, the window reverts on its own after
relaxedWindowMaxDuration (default 60 s). A MOVING window has no closing notification: relaxation
applies to every pool connection toward the affected endpoint and ends when the announced grace
period expires.

Pre-handoff (`MOVING`)

During the grace period:

  • Commands in flight on existing connections complete normally under the relaxed timeouts.
  • New connections — including replacements for returned ones — are opened against the endpoint
    carried by the notification and inherit the relaxed timeouts.
  • Connections to the old endpoint are retired: each is discarded when returned to the pool.
  • A connection held past the grace period is disconnected by the server; using it throws
    JedisConnectionException, and returning it replaces it with a fresh connection.

Endpoint types

The client tells the server which address format to return in MOVING. By default this is
auto-resolved per connection: a private remote IP requests INTERNAL_*, a public one EXTERNAL_*;
TLS requests *_FQDN, plaintext *_IP. A fixed type can be requested with
MaintenanceNotificationsConfig.builder().endpointType(...), including NONE — the notification
then carries no target and the client schedules a reconnect to its configured endpoint at half the
grace period.

When choosing a fixed type, make sure the requested address family is routable from where the
client runs — an internal IP is typically not reachable from outside the cluster's network.

See the official SCH documentation for the
cross-client overview, server-side enablement, and product-specific limitations.

Pub/Sub

A subscribed connection consumes maintenance notifications inline between pub/sub messages, so
relaxed timeouts apply to it like to any other connection. Pre-handoff, however, does not: the
subscribed connection is held by the listen loop and cannot be replaced underneath the
application, so on MOVING it stays on the old endpoint and is disconnected by the server when
the grace period expires. The subscription then fails with JedisConnectionException;
re-subscribing — on a fresh connection, which reaches the new endpoint — is the application's
responsibility.

Production notes

  • JVM DNS caching. After the grace period the client reconnects using the configured endpoint
    name. The JVM caches successful DNS lookups for a fixed 30 seconds by default (ignoring record
    TTLs), which can outlive the grace period. Disable the cache or set it to a low value via the
    networkaddress.cache.ttl
    security property.

Transactions Multi (`docs/transactions-multi.md`)

Transactions/Multi

Overview

Transactions guarantee that all the included commands will execute to completion without being interrupted by commands from other clients. See the Transactions page for more information.

To execute commands in a transaction, create a transaction object with the multi() command, call command methods on that object, and then call the transaction object's exec() method to execute it. You can access the results from commands in the transaction using Response objects. The exec() method also returns a List<Object> value that contains all the result values in the order the commands were executed.

Immediate Transaction Start

The simplest way to use transactions is to call multi() on your Jedis client, which immediately starts a transaction by sending the MULTI command to Redis. All subsequent commands are queued until exec() is called.

java
import redis.clients.jedis.RedisClient;
import redis.clients.jedis.AbstractTransaction;
import redis.clients.jedis.Response;
import java.util.List;

RedisClient jedis = RedisClient.create("redis://localhost:6379");

// Create a transaction that immediately sends MULTI
try (AbstractTransaction tx = jedis.multi()) {
    // Commands are queued
    Response<String> set1 = tx.set("counter:1", "0");
    Response<Long> incr1 = tx.incrBy("counter:1", 1);
    Response<Long> incr2 = tx.incrBy("counter:1", 2);
    
    // Execute the transaction
    List<Object> results = tx.exec();
    
    // Access results via Response objects
    System.out.println(incr1.get()); // 1
    System.out.println(incr2.get()); // 3
    
    // Or via the results list
    System.out.println(results.get(0)); // OK
    System.out.println(results.get(1)); // 1
    System.out.println(results.get(2)); // 3
}

jedis.close();

Response Handling

Commands invoked within a transaction return Response<T> objects. These responses become available only after exec() is called:

  • Before exec(): Calling response.get() will throw IllegalStateException with the message "Please close pipeline or multi block before calling this method."
  • After exec(): Response objects contain the actual results from Redis

The exec() method returns a List<Object> containing all command results in the order they were queued.

Manual Transaction Start

For more control, you can create a transaction without immediately sending MULTI. This is useful when you need to:

  • Execute commands before starting the transaction
  • Use WATCH to implement optimistic locking
  • Conditionally start the transaction

Create a manual transaction by passing false to the transaction() method:

java
RedisClient jedis = RedisClient.create("redis://localhost:6379");

// Create transaction without sending MULTI
try (AbstractTransaction tx = jedis.transaction(false)) {
    
    // Commands before multi() are executed immediately
    Response<String> setBeforeMulti = tx.set("mykey", "initial_value");
    Response<String> getBeforeMulti = tx.get("mykey");
    
    // These responses are available immediately
    System.out.println(setBeforeMulti.get()); // OK
    System.out.println(getBeforeMulti.get()); // initial_value
    
    // Now start the transaction
    tx.multi();
    
    // Commands after multi() are queued
    Response<String> set = tx.set("mykey", "new_value");
    Response<String> get = tx.get("mykey");
    
    // Execute the transaction
    List<Object> results = tx.exec();
    
    // Results from queued commands
    System.out.println(set.get()); // OK
    System.out.println(get.get()); // new_value
}

jedis.close();

Using WATCH for Optimistic Locking

The WATCH command monitors keys for changes. If any watched key is modified before EXEC, the transaction is aborted and exec() returns null.

Important: WATCH must be executed through the transaction object to ensure it uses the transaction's dedicated connection.

java
try (AbstractTransaction tx = jedis.transaction(false)) {
    // WATCH must be on the transaction's dedicated connection
    tx.watch("counter");

    // Read current value - can use the client directly
    String current = jedis.get("counter");
    int newValue = Integer.parseInt(current) + 1;

    // Start transaction and queue update
    tx.multi();
    tx.set("counter", String.valueOf(newValue));

    // Returns null if key was modified by another client
    List<Object> results = tx.exec();

    if (results == null) {
        System.out.println("Transaction aborted - key was modified");
    }
}

Connection Lifecycle

A transaction acquires a dedicated connection that is held until close() is called. Ensure the transaction is closed on error:

java
try (AbstractTransaction tx = jedis.multi()) {
    tx.set("key", "value");
    tx.exec();
}

Important: WATCH must be executed through the transaction object to ensure it uses the transaction's dedicated connection.

Transaction Completion

Complete a transaction by calling either:

  • exec() - Executes all queued commands atomically and returns a List<Object> with the results
  • discard() - Discards all queued commands without executing them

Automatic Cleanup

When using try-with-resources, close() automatically sends DISCARD (if in MULTI state) or UNWATCH (if in WATCH state) to ensure the connection is returned to the pool in a clean state.

Notes on `MultiDbTransaction`

MultiDbTransaction (returned by multi() / transaction(...) on a multi-database client) is annotated @Experimental and behaves slightly differently from the single-endpoint transaction.

Connection Lifecycle

  • When started with multi() (i.e. doMulti=true), connection acquisition is deferred until exec() (or discard() / close()). Commands issued after MULTI are buffered in memory only.
  • When started with transaction(false) (i.e. doMulti=false), a connection is acquired on the first command that needs to be sent to the server (e.g. watch(...) or any pre-MULTI read/write) and held until the transaction is completed.
  • A MultiDbTransaction is bound to the database that was active at the time the connection was acquired, and the connection is borrowed from that database's pool. Subsequent database failovers do not migrate the in-flight transaction to the new endpoint.
  • On exec(), discard() and close() the held connection is released back to its originating pool (via Connection.close()) in the finally block, regardless of success or failure. The next operation after release will use a fresh connection from the currently active database.

Database-switch Semantics

If a failover changes the active database while a MultiDbTransaction is in progress, the transaction will not silently complete against a different endpoint:

  • For commands sent before MULTI (pre-MULTI traffic on a manual transaction), the next appendCommand call detects the switch and throws JedisException("Active database has changed since transaction started") without sending the command.
  • On exec(), after queueing commands on the server, the active database is re-checked. If it has changed, the transaction sends DISCARD to the original connection so the server-side MULTI block is rolled back, then throws JedisException("Active database has changed since transaction started"). Any error from the DISCARD attempt is attached as a suppressed exception.
  • In both cases the held connection is still released via the normal finally-block path, so it is returned to its (original) pool and subsequent operations go through the now-active database.

Tutorials Examples (`docs/tutorials_examples.md`)

Tutorials and Examples

General

Client-side Caching

JSON


Verifying Artifacts (`docs/verifying-artifacts.md`)

Verifying contents

Jedis artifacts published on Maven central are signed. For each artifact, there is an associated signature file with the .asc suffix.

Keys used for signing

text
pub   rsa3072 2023-12-17 [SC]
165E63A75659104C72B49129C2B44BE148BDCEC8
uid           [ unknown] Redis OSS <[email protected]>
sig 3        C2B44BE148BDCEC8 2023-12-17  [self-signature]

A copy of this key is stored on the keyserver Ubuntu keyserver

Before 2023-12-17

text
pub   rsa3072 2023-09-03 [SC]
6BF6C1905C9642D1181D157443543D884CC71C96
uid           [ unknown] Redis OSS <[email protected]>
sig 3        43543D884CC71C96 2023-09-03  [self-signature]

A copy of this key is stored on the keyserver Ubuntu keyserver

Before 2023-09-03

text
pub   rsa3072 2021-06-27 [SC] [expired: 2023-06-27]
      1C913D6D36DAD0A679863F5CB68CD0A11D28B97D
uid           [ expired] RedisLabs OSS <[email protected]>
sig 3        B68CD0A11D28B97D 2021-06-27  [self-signature]

A copy of this key is stored on the keyserver Ubuntu keyserver

Before 2021-06-27

text
pub   rsa3072 2018-08-12 [SC] [expired: 2020-08-11]
      4EC78BD38D30A2E85C02C39C2D62B50EF8D3297A
uid           [ expired] Guy Korlad <[email protected]>
sig 3        2D62B50EF8D3297A 2018-08-12  [self-signature]

A copy of this key is stored on the keyserver Ubuntu keyserver


Migration Guides V3 To V4 Primitives (`docs/migration-guides/v3-to-v4-primitives.md`)

The following methods now return primitive values:

>> long/boolean/double instead of Long/Boolean/Double:

  • dbSize()
  • lastsave()
  • slowlogLen()
  • clientId()
  • clientUnblock(long clientId, UnblockType unblockType)
  • clientKill(ClientKillParams params)
  • aclDelUser(String name)
  • aclDelUser(byte[] name)
  • move(String key, int dbIndex)
  • move(byte[] key, int dbIndex)
  • waitReplicas(int replicas, long timeout)
  • waitReplicas(String key, int replicas, long timeout)
  • exists(String key)
  • exists(byte[] key)
  • exists(String... keys)
  • exists(byte[]... keys)
  • persist(String key)
  • expire(String key, long seconds)
  • expire(byte[] key, long seconds)
  • pexpire(String key, long milliseconds)
  • pexpire(byte[] key, long milliseconds)
  • expireAt(String key, long unixTime)
  • expireAt(byte[] key, long unixTime)
  • pexpireAt(String key, long millisecondsTimestamp)
  • pexpireAt(byte[] key, long millisecondsTimestamp)
  • ttl(String key)
  • ttl(byte[] key)
  • pttl(String key)
  • touch(String key)
  • touch(byte[] key)
  • touch(String... keys)
  • touch(byte[]... keys)
  • setbit(String key, long offset, boolean value)
  • setbit(byte[] key, long offset, boolean value)
  • getbit(String key, long offset)
  • getbit(byte[] key, long offset)
  • setrange(String key, long offset, String value)
  • setrange(byte[] key, long offset, byte[] value)
  • setnx(String key, String value)
  • setnx(byte[] key, byte[] value)
  • incr(String key)
  • incr(byte[] key)
  • decr(String key)
  • decr(byte[] key)
  • incrBy(String key, long increment)
  • incrBy(byte[] key, long increment)
  • decrBy(String key, long decrement)
  • decrBy(byte[] key, long decrement)
  • incrByFloat(String key, double increment)
  • incrByFloat(byte[] key, double increment)
  • append(String key, String value)
  • append(byte[] key, byte[] value)
  • hset(String key, String field, String value)
  • hset(byte[] key, byte[] field, byte[] value)
  • hset(String key, Map<String, String> hash)
  • hset(byte[] key, Map hash)
  • hsetnx(String key, String field, String value)
  • hsetnx(byte[] key, byte[] field, byte[] value)
  • hincrBy(String key, String field, long value)
  • hincrBy(byte[] key, byte[] field, long value)
  • hincrByFloat(String key, String field, double value)
  • hincrByFloat(byte[] key, byte[] field, double value)
  • hexists(String key, String field)
  • hexists(byte[] key, byte[] field)
  • hdel(String key, String... field)
  • hdel(byte[] key, byte[]... field)
  • hlen(String key)
  • hlen(byte[] key)
  • rpush(String key, String... string)
  • rpush(byte[] key, byte[]... args)
  • lpush(String key, String... string)
  • lpush(byte[] key, byte[]... args)
  • llen(String key)
  • llen(byte[] key)
  • lrem(String key, long count, String value)
  • lrem(byte[] key, long count, byte[] value)
  • sadd(String key, String... member)
  • sadd(byte[] key, byte[]... member)
  • scard(String key)
  • scard(byte[] key)
  • sismember(String key, String member)
  • sismember(byte[] key, byte[] member)
  • strlen(String key)
  • strlen(byte[] key)
  • zadd(String key, double score, String member)
  • zadd(byte[] key, double score, byte[] member)
  • zadd(String key, double score, String member, ZAddParams params)
  • zadd(byte[] key, double score, byte[] member, ZAddParams params)
  • zadd(String key, Map<String, Double> scoreMembers)
  • zadd(byte[] key, Map scoreMembers)
  • zadd(String key, Map<String, Double> scoreMembers, ZAddParams params)
  • zadd(byte[] key, Map scoreMembers, ZAddParams params)
  • zrem(String key, String... members)
  • zrem(byte[] key, byte[]... members)
  • zincrby(String key, double increment, String member)
  • zincrby(byte[] key, double increment, byte[] member)
  • zcard(String key)
  • zcard(byte[] key)
  • zcount(String key, double min, double max)
  • zcount(byte[] key, double min, double max)
  • zcount(String key, String min, String max)
  • zcount(byte[] key, byte[] min, byte[] max)
  • zremrangeByRank(String key, long start, long stop)
  • zremrangeByRank(byte[] key, long start, long stop)
  • zremrangeByScore(String key, double min, double max)
  • zremrangeByScore(byte[] key, double min, double max)
  • zremrangeByScore(String key, String min, String max)
  • zremrangeByScore(byte[] key, byte[] min, byte[] max)
  • zlexcount(String key, String min, String max)
  • zlexcount(byte[] key, byte[] min, byte[] max)
  • zremrangeByLex(String key, String min, String max)
  • zremrangeByLex(byte[] key, byte[] min, byte[] max)
  • linsert(String key, ListPosition where, String pivot, String value)
  • linsert(byte[] key, ListPosition where, byte[] pivot, byte[] value)
  • lpushx(String key, String... string)
  • lpushx(byte[] key, byte[]... arg)
  • rpushx(String key, String... string)
  • rpushx(byte[] key, byte[]... arg)
  • del(String key)
  • del(byte[] key)
  • del(String... keys)
  • unlink(String key)
  • unlink(byte[] key)
  • unlink(String... keys)
  • bitcount(String key)
  • bitcount(byte[] key)
  • bitcount(String key, long start, long end)
  • bitcount(byte[] key, long start, long end)
  • bitpos(String key, boolean value)
  • bitpos(String key, boolean value, BitPosParams params)
  • pfadd(String key, String... elements)
  • pfadd(byte[] key, byte[]... elements)
  • pfcount(byte[]... keys)
  • geoadd(String key, double longitude, double latitude, String member)
  • geoadd(byte[] key, double longitude, double latitude, byte[] member)
  • geoadd(String key, Map<String, GeoCoordinate> memberCoordinateMap)
  • geoadd(byte[] key, Map memberCoordinateMap)
  • geoadd(String key, GeoAddParams params, Map<String, GeoCoordinate> memberCoordinateMap)
  • geoadd(byte[] key, GeoAddParams params, Map memberCoordinateMap)
  • hstrlen(String key, String field)
  • hstrlen(byte[] key, byte[] field)
  • xlen(String key)
  • xlen(byte[] key)
  • xack(String key, String group, StreamEntryID... ids)
  • xack(byte[] key, byte[] group, byte[]... ids)
  • xgroupDestroy(String key, String groupname)
  • xgroupDestroy(byte[] key, byte[] consumer)
  • xgroupDelConsumer( String key, String groupname, String consumername)
  • xgroupDelConsumer(byte[] key, byte[] consumer, byte[] consumerName)
  • xdel(String key, StreamEntryID... ids)
  • xdel(byte[] key, byte[]... ids)
  • xtrim(String key, long maxLen, boolean approximate)
  • xtrim(byte[] key, long maxLen, boolean approximateLength)
  • xtrim(String key, XTrimParams params)
  • xtrim(byte[] key, XTrimParams params)
  • clusterKeySlot(String key)
  • clusterCountKeysInSlot(int slot)
  • msetnx(String... keysvalues)
  • msetnx(byte[]... keysvalues)
  • renamenx(String oldkey, String newkey)
  • renamenx(byte[] oldkey, byte[] newkey)
  • sdiffstore(String dstkey, String... keys)
  • sdiffstore(byte[] dstkey, byte[]... keys)
  • sinterstore(String dstkey, String... keys)
  • sinterstore(byte[] dstkey, byte[]... keys)
  • smove(String srckey, String dstkey, String member)
  • smove(byte[] srckey, byte[] dstkey, byte[] member)
  • sort(String key, String dstkey)
  • sort(byte[] key, byte[] dstkey)
  • sort(String key, SortingParams sortingParameters, String dstkey)
  • sort(byte[] key, SortingParams sortingParameters, byte[] dstkey)
  • sunionstore(String dstkey, String... keys)
  • sunionstore(byte[] dstkey, byte[]... keys)
  • zdiffStore(String dstkey, String... keys)
  • zdiffStore(byte[] dstkey, byte[]... keys)
  • zinterstore(String dstkey, String... sets)
  • zinterstore(byte[] dstkey, byte[]... sets)
  • zinterstore(String dstkey, ZParams params, String... sets)
  • zinterstore(byte[] dstkey, ZParams params, byte[]... sets)
  • zunionstore(String dstkey, String... sets)
  • zunionstore(byte[] dstkey, byte[]... sets)
  • zunionstore(String dstkey, ZParams params, String... sets)
  • zunionstore(byte[] dstkey, ZParams params, byte[]... sets)
  • bitop(BitOP op, String destKey, String... srcKeys)
  • bitop(BitOP op, byte[] destKey, byte[]... srcKeys)
  • georadiusStore(String key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam)
  • georadiusStore(byte[] key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam)
  • georadiusByMemberStore(String key, String member, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam)
  • georadiusByMemberStore(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam)
  • copy(String srcKey, String dstKey, int db, boolean replace)
  • copy(byte[] srcKey, byte[] dstKey, int db, boolean replace)
  • copy(String srcKey, String dstKey, boolean replace)
  • copy(byte[] srcKey, byte[] dstKey, boolean replace)

Migration Guides V3 To V4 Zset List (`docs/migration-guides/v3-to-v4-zset-list.md`)

Each of the following sorted set methods now return a Java `List` instead of a `Set`:

  • zrange(byte[] key, long start, long stop)
  • zrange(String key, long start, long stop)
  • zrevrange(byte[] key, long start, long stop)
  • zrevrange(String key, long start, long stop)
  • zrangeWithScores(byte[] key, long start, long stop)
  • zrangeWithScores(String key, long start, long stop)
  • zrevrangeWithScores(byte[] key, long start, long stop)
  • zrevrangeWithScores(String key, long start, long stop)
  • zrandmember(byte[] key, long count)
  • zrandmember(String key, long count)
  • zrandmemberWithScores(byte[] key, long count)
  • zrandmemberWithScores(String key, long count)
  • zpopmax(byte[] key, int count)
  • zpopmax(String key, int count)
  • zpopmin(byte[] key, int count)
  • zpopmin(String key, int count)
  • zrangeByScore(byte[] key, double min, double max)
  • zrangeByScore(String key, double min, double max)
  • zrangeByScore(byte[] key, byte[] min, byte[] max)
  • zrangeByScore(String key, String min, String max)
  • zrevrangeByScore(byte[] key, double max, double min)
  • zrevrangeByScore(String key, double max, double min)
  • zrangeByScore(byte[] key, double min, double max, int offset, int count)
  • zrangeByScore(String key, double min, double max, int offset, int count)
  • zrevrangeByScore(byte[] key, byte[] max, byte[] min)
  • zrevrangeByScore(String key, String max, String min)
  • zrangeByScore(byte[] key, byte[] min, byte[] max, int offset, int count)
  • zrangeByScore(String key, String min, String max, int offset, int count)
  • zrevrangeByScore(byte[] key, double max, double min, int offset, int count)
  • zrevrangeByScore(String key, double max, double min, int offset, int count)
  • zrangeByScoreWithScores(byte[] key, double min, double max)
  • zrangeByScoreWithScores(String key, double min, double max)
  • zrevrangeByScoreWithScores(byte[] key, double max, double min)
  • zrevrangeByScoreWithScores(String key, double max, double min)
  • zrangeByScoreWithScores(byte[] key, double min, double max, int offset, int count)
  • zrangeByScoreWithScores(String key, double min, double max, int offset, int count)
  • zrevrangeByScore(byte[] key, byte[] max, byte[] min, int offset, int count)
  • zrevrangeByScore(String key, String max, String min, int offset, int count)
  • zrangeByScoreWithScores(byte[] key, byte[] min, byte[] max)
  • zrangeByScoreWithScores(String key, String min, String max)
  • zrevrangeByScoreWithScores(byte[] key, byte[] max, byte[] min)
  • zrevrangeByScoreWithScores(String key, String max, String min)
  • zrangeByScoreWithScores(byte[] key, byte[] min, byte[] max, int offset, int count)
  • zrangeByScoreWithScores(String key, String min, String max, int offset, int count)
  • zrevrangeByScoreWithScores(byte[] key, double max, double min, int offset, int count)
  • zrevrangeByScoreWithScores(String key, double max, double min, int offset, int count)
  • zrevrangeByScoreWithScores(byte[] key, byte[] max, byte[] min, int offset, int count)
  • zrevrangeByScoreWithScores(String key, String max, String min, int offset, int count)
  • zrangeByLex(byte[] key, byte[] min, byte[] max)
  • zrangeByLex(String key, String min, String max)
  • zrangeByLex(byte[] key, byte[] min, byte[] max, int offset, int count)
  • zrangeByLex(String key, String min, String max, int offset, int count)
  • zrevrangeByLex(byte[] key, byte[] max, byte[] min)
  • zrevrangeByLex(String key, String max, String min)
  • zrevrangeByLex(byte[] key, byte[] max, byte[] min, int offset, int count)
  • zrevrangeByLex(String key, String max, String min, int offset, int count)

Migration Guides V3 To V4 (`docs/migration-guides/v3-to-v4.md`)

Jedis 4 Breaking Changes

  • The BinaryJedis and BinaryJedisCluster classes have been removed.

    The methods from these classes are available in the Jedis and JedisCluster classes
    respectively.

  • The following cases now throws an IllegalStateException instead of a
    JedisDataException.

    • Cannot use Jedis when in Multi. Please use Transaction or reset Jedis state.
    • Cannot use Jedis when in Pipeline. Please use Pipeline or reset Jedis state.
  • The Redis transaction methods multi(), exec() and discard() have
    been removed from Pipeline.

  • The execGetResponse() method has been removed from Transaction.

  • The watch() and unwatch() methods from the Transaction class are unsupported
    within MULTI (i.e., after the multi() method). However, watch() and unwatch () can still be used before calling MULTI.

  • The JedisCluster constructors with GenericObjectPoolConfig<Jedis> now accept
    GenericObjectPoolConfig<Connection>.

  • All JedisCluster constructors now throw a JedisClusterOperationException if
    unable to connect to any of the provided HostAndPort(s). Previously, the
    connection would go into an unusable state.

  • JedisCluster.getClusterNodes() returns Map<String, ConnectionPool> instead of
    Map<String, JedisPool>.

  • JedisCluster.getConnectionFromSlot(int) returns Connection instead of Jedis.

  • JedisNoReachableClusterNodeException has been removed.
    JedisClusterOperationException, with a similar message, is thrown instead.

  • JedisClusterMaxAttemptsException has been removed.
    JedisClusterOperationException, with a similar message, is thrown instead.

  • JedisExhaustedPoolException has been removed. A JedisException with a similar message is thrown
    instead.

  • Many sorted set methods return a Java List instead of a
    Set. See the complete list.

  • Many methods return primitive values) (long/boolean/double instead of
    Long/Boolean/
    Double). See the complete list.

  • scriptExists(byte[]) method now returns Boolean instead of Long.

  • scriptExists(byte[]...) method now returns List instead of List<Long>.

  • In the xadd method with StreamEntryID
    parameter, sending untyped null raises an exception.

    Casting the null to StreamEntryID ((StreamEntryID) null) resolves this issue.

  • In the xrange and
    xrevrange methods with StreamEntryID parameters, sending
    untyped nulls for both start and end parameters raises an exception.

    Casting the nulls to StreamEntryID ((StreamEntryID) null) resolves this issue.

  • The return type of Jedis.shutdown() is now void. Previously, it would return null.

  • The eval and evalsha methods are now non-blocking. These methods were
    blocking in Jedis 3.x.

  • The HostAndPort.localhost constant has been removed.

  • The following methods have been removed from HostAndPort class:

    • extractParts()
    • parseString()
    • convertHost()
    • setLocalhost()
    • getLocalhost()
    • getLocalHostQuietly()
  • The following classes have been moved to the redis.clients.jedis.args package.

    • BitOP
    • GeoUnit
    • ListPosition
  • The following classes have been moved to the redis.clients.jedis.params package.

    • BitPosParams
    • ScanParams
    • SortingParams
    • ZParams
  • The following classes have been moved to the redis.clients.jedis.resps package.

    • AccessControlLogEntry
    • AccessControlUser
    • GeoRadiusResponse
    • ScanResult
    • Slowlog
    • StreamConsumersInfo
    • StreamEntry
    • StreamGroupInfo
    • StreamInfo
    • StreamPendingEntry
    • StreamPendingSummary
    • Tuple
  • Jedis and JedisPool constructors with a String parameter, and no int
    parameter, only support a URL or URI string.

    • Jedis(String)
    • JedisPool(String)
    • JedisPool(String, SSLSocketFactory, SSLParameters, HostnameVerifier)
    • JedisPool(GenericObjectPoolConfig, String)
  • The Client and BinaryClient classes have been removed.

  • redis.clients.jedis.commands package has been reimplemented, meaning that the
    Commands interfaces have been restructured.

  • The ShardedJedisPool, Sharded, ShardedJedis, BinaryShardedJedis, ShardInfo,
    JedisShardInfo classes have been removed.

    • Introduced JedisSharding class to replace ShardedJedisPool.

      Earlier code without the use of "name" and "weight" (in ShardInfo/JedisShardInfo) are
      transferable to the new class.

  • ShardedJedisPipeline class has been removed.

    • Introduced ShardedPipeline class to replace ShardedJedisPipeline.
  • The type of Protocol.CHARSET has been changed to java.nio.charset.Charset.

  • Jedis.debug(DebugParams) method has been removed.

  • The DebugParams class has been removed.

  • The Jedis.sync() method has been removed.

  • The Jedis.pubsubNumSub(String...) method now returns Map<String, Long>
    instead of Map<String, String>.

  • setDataSource method in Jedis class now has protected access.

  • JedisPoolAbstract class has been removed. Use Pool<Jedis>.

  • The Pool.initPool() method has been removed.

  • The Pool.getNumActive() method now returns 0 (via GenericObjectPool) when the
    pool is closed.

  • The Connection.getRawObjectMultiBulkReply() method has been removed. Use
    Connection.getUnflushedObjectMultiBulkReply() method.

  • The Queable.getResponse(Builder<T> builder) method has been renamed to
    Queable.enqueResponse(Builder<T> builder).

  • All methods in Queable are now final:

    • clean()
    • generateResponse(Object data)
    • enqueResponse(Builder<T> builder)
    • getPipelinedResponseLength()
  • These BuilderFactory implementations have been removed:

    • OBJECT (use RAW_OBJECT)
    • BYTE_ARRAY_ZSET (use BINARY_LIST or BINARY_SET)
    • BYTE_ARRAY_MAP (use BINARY_MAP)
    • STRING_ZSET (use STRING_LIST or STRING_SET)
    • EVAL_RESULT (use ENCODED_OBJECT)
    • EVAL_BINARY_RESULT (use RAW_OBJECT)
  • All String variables representing Cluster, Sentinel and PubSub subcommands in Protocol class
    have been removed.

  • ClientKillParams.Type has been removed. Use ClientType.

  • ClusterReset has been removed. Use ClusterResetType.

  • The JedisClusterHostAndPortMap interface has been removed. Use the
    HostAndPortMapper interface.

  • JedisClusterHashTagUtil class has been renamed to JedisClusterHashTag.

  • The KeyMergeUtil class has been removed.


Migration Guides V4 To V5 (`docs/migration-guides/v4-to-v5.md`)

Jedis 5 Breaking Changes

  • All variants of blmpop and bzmpop methods now take double timeout parameter instead of long timeout parameter.
    This is breaking ONLY IF you are using Long for timeout.

  • Reducer abstract class is refactored:

    • Reducer(String field) constructor is removed; Reducer(String name, String field) constructor is added.
    • Reducer(String name) constructor is added; it will cause runtime error with older Reducer(String field) constructor.
    • getName method is removed.
    • getAlias method is removed.
    • setAlias method is removed; use as method.
    • setAliasAsField method is removed.
    • getOwnArgs method is now abstract.
    • getArgs method is removed.
  • quit() method has been removed from Connection and ServerCommands interface and implementations.

  • updatePassword(String password) method has been removed from JedisClientConfig and implementations.

  • setPassword(String password) method has been removed from both JedisFactory and ConnectionFactory classes.

  • Both bzpopmax(double timeout, String... keys) and bzpopmin(double timeout, String... keys) now return KeyValue<String, Tuple> (instead of KeyedZSetElement).

  • Both bzpopmax(double timeout, byte[]... keys) and bzpopmin(double timeout, byte[]... keys) now return KeyValue (instead of List).

  • Following methods now return KeyValue<String, String> instead of KeyedListElement:

    • blpop(double timeout, String key)
    • blpop(double timeout, String... keys)
    • brpop(double timeout, String key)
    • brpop(double timeout, String... keys)
  • Following methods now return KeyValue instead of List:

    • blpop(double timeout, byte[]... keys)
    • brpop(double timeout, byte[]... keys)
  • zdiff(String... keys) method now returns List<String> (instead of Set<String>).

  • zdiff(byte[]... keys) method now returns List (instead of Set).

  • Both zdiffWithScores(String... keys) and zdiffWithScores(byte[]... keys) methods now return List<Tuple> (instead of Set<Tuple>).

  • zinter(ZParams params, String... keys) method now returns List<String> (instead of Set<String>).

  • zinter(ZParams params, byte[]... keys) method now returns List (instead of Set).

  • Both zinterWithScores(ZParams params, String... keys) and zinterWithScores(ZParams params, byte[]... keys) methods now return List<Tuple> (instead of Set<Tuple>).

  • zunion(ZParams params, String... keys) method now returns List<String> (instead of Set<String>).

  • zunion(ZParams params, byte[]... keys) method now returns List (instead of Set).

  • Both zunionWithScores(ZParams params, String... keys) and zunionWithScores(ZParams params, byte[]... keys) methods now return List<Tuple> (instead of Set<Tuple>).

  • Both configGet(String pattern) and configGet(String... patterns) methods now return Map<String, String> instead of List<String>.

  • Both configGet(byte[] pattern) and configGet(byte[]... patterns) methods now return Map instead of List.

  • New aclDelUser(String... names) method replaces aclDelUser(String name) and aclDelUser(String name, String... names) methods.

  • New aclDelUser(byte[]... names) method replaces aclDelUser(byte[] name) and aclDelUser(byte[] name, byte[]... names) methods.

  • tsMGet(TSMGetParams multiGetParams, String... filters) method now returns Map<String, TSMGetElement> instead of List<TSKeyValue<TSElement>>.

  • Following methods now return Map<String, TSMRangeElements> instead of List<TSKeyedElements>:

    • tsMRange(long fromTimestamp, long toTimestamp, String... filters)
    • tsMRange(TSMRangeParams multiRangeParams)
    • tsMRevRange(long fromTimestamp, long toTimestamp, String... filters)
    • tsMRevRange(TSMRangeParams multiRangeParams)
  • jsonNumIncrBy(String key, Path2 path, double value) method now returns Object instead of JSONArray.

    • The returning object would still be JSONArray for all previous cases. So simple type casting is enough to handle this change.
    • The returning object will be List<Double> when running under RESP3 protocol.
  • getAgeSeconds() in AccessControlLogEntry now returns Double instead of String.

  • Both ftConfigGet(String option) and ftConfigGet(String indexName, String option) methods now return Map<String, Object> instead of Map<String, String>.

  • ftList() method now returns Set<String> instead of List<String>.

  • graphSlowlog(String graphName) now returns List<List<Object>> (instead of List<List<String>>).

  • CommandListFilterByParams now throws IllegalArgumentException (instead of JedisDataException) in case of unfulfilling filter.

  • FailoverParams now throws IllegalArgumentException (instead of IllegalStateException) in case of unfulfilling optional arguments.

  • XPendingParams now throws IllegalArgumentException (instead of IllegalStateException) in case of unfulfilling optional arguments.

  • get() option has been removed from SetParams. Following methods have been added in Jedis/UnifiedJedis for convenience:

    • setGet(String key, String value) method has been added in StringCommands interface.
    • setGet(byte[] key, byte[] value) method has been added in StringBinaryCommands interface.
  • xpending(String key, String groupName, StreamEntryID start, StreamEntryID end, int count, String consumerName) method has been removed from everywhere.

    • Use xpending(java.lang.String, java.lang.String, redis.clients.jedis.params.XPendingParams) instead.
  • xpending(byte[] key, byte[] groupName, byte[] start, byte[] end, int count, byte[] consumerName) method has been removed from everywhere.

    • Use xpending(byte[], byte[], redis.clients.jedis.params.XPendingParams) instead.
  • retentionTime(long retentionTime) method in TSAlterParams has been removed. Use retention(long) method instead.

  • Following classes have been removed:

    • KeyedZSetElement
    • KeyedListElement
    • TSKeyValue
    • TSKeyedElements
    • Limit
  • Following BuilderFactory implementations have been removed:

    • BYTE_ARRAY (use BINARY)
    • BYTE_ARRAY_LIST (use BINARY_LIST)
    • BINARY_MAP_FROM_PAIRS
    • STRING_ORDERED_SET
  • All payload related parameters are removed from search related classes; namely Document, IndexDefinition, Query.

  • topkCount(String key, String... items) method has been removed from everywhere.

  • Following methods supporting JSON.RESP command have been removed:

    • jsonResp(String key)
    • jsonResp(String key, Path path)
    • jsonResp(String key, Path2 path)
  • RedisJsonCommands and RedisJsonPipelineCommands interfaces have been moved into redis.clients.jedis.json.commands package.

  • AbortedTransactionException is removed.

  • Queable class is removed.

  • Params abstract class is removed.

    • toString() support used by its sub-classes is now unavailable.
  • getParams() method is removed from SortingParams class.

  • Both SEARCH_AGGREGATION_RESULT and SEARCH_AGGREGATION_RESULT_WITH_CURSOR implementations from SearchBuilderFactory class have been moved to AggregationResult class.

  • All AggregationResult constructors have been made private.

  • getArgs(), getArgsString() and serializeRedisArgs(List redisArgs) methods have been removed from AggregationBuilder.

  • totalResults variable in AggregationResult has been made private. Use getTotalResults() method instead.

  • getArgs() and limit(Limit limit) methods have been removed from Group class.

  • addCommandEncodedArguments and addCommandBinaryArguments methods have been removed from FieldName class.

  • addObjects(int[] ints) method has been removed from CommandArguments.

  • Following methods have been removed:

    • strAlgoLCSStrings(String strA, String strB, StrAlgoLCSParams params)
    • strAlgoLCSStrings(byte[] strA, byte[] strB, StrAlgoLCSParams params)
    • strAlgoLCSKeys(String keyA, String keyB, StrAlgoLCSParams params)
    • strAlgoLCSKeys(byte[] keyA, byte[] keyB, StrAlgoLCSParams params)
  • StrAlgoLCSParams class has been removed.

  • Following methods have been removed from all Pipeline classes:

    • ftCursorRead(String indexName, long cursorId, int count)
    • ftCursorDel(String indexName, long cursorId)
    • ftDropIndex(String indexName)
    • ftDropIndexDD(String indexName)
    • ftAliasAdd(String aliasName, String indexName)
    • ftAliasUpdate(String aliasName, String indexName)
    • ftAliasDel(String aliasName)
  • JedisSentineled(String masterName, Set<HostAndPort> sentinels, JedisClientConfig masterClientConfig, JedisClientConfig sentinelClientConfig) and
    JedisSentineled(String masterName, Set<HostAndPort> sentinels, GenericObjectPoolConfig<Connection> poolConfig, JedisClientConfig masterClientConfig, JedisClientConfig sentinelClientConfig)
    constructors have been removed.

  • JedisClusterInfoCache(JedisClientConfig clientConfig) and JedisClusterInfoCache(JedisClientConfig clientConfig, GenericObjectPoolConfig<Connection> poolConfig)
    constructors have been removed.


Migration Guides V5 To V6 (`docs/migration-guides/v5-to-v6.md`)

Jedis 6.0.0 Migration Guide

This guide helps you migrate from Jedis 5.x to Jedis 6.0.0. Version 6.0.0 includes breaking changes focused on Redis 8.0 support, removal of deprecated modules, and improvements to the Search API.

Table of Contents

Overview

Jedis 6.0.0 is a major release that adds Redis 8.0 support and removes deprecated features. The main focus areas are:

  1. Redis 8.0 compatibility - Full support for Redis 8.0 features including built-in JSON, Search, and TimeSeries
  2. Module deprecations - Removal of RedisGraph and Triggers & Functions (RedisGears v2) support
  3. Search API improvements - Default dialect change and enhanced profiling responses
  4. Security enhancements - New SSL options and token-based authentication support

Breaking Changes

Removed RedisGraph Support

RedisGraph module support has been completely removed from Jedis 6.0.0 as the module has been deprecated by Redis.

Removed Classes and Interfaces

All classes in the redis.clients.jedis.graph package have been removed:

  • RedisGraphCommands interface
  • RedisGraphPipelineCommands interface
  • GraphCommandObjects class
  • GraphCache, GraphProtocol, GraphQueryParams classes
  • ResultSet, ResultSetBuilder, Record, Header, Statistics classes
  • All entity classes: Edge, Node, Path, Point, Property, GraphEntity

Removed Triggers and Functions (RedisGears v2)

Support for Triggers and Functions (RedisGears v2) has been removed from Jedis 6.0.0.

Removed Classes and Interfaces

All classes in the redis.clients.jedis.gears package have been removed:

  • RedisGearsCommands interface
  • RedisGearsProtocol class
  • TFunctionListParams, TFunctionLoadParams classes
  • Response classes: FunctionInfo, FunctionStreamInfo, GearsLibraryInfo, StreamTriggerInfo, TriggerInfo

Search Dialect Default Change

BREAKING: The default search dialect has changed from server-side default to DIALECT 2 (client-side override).

Impact

Starting with Jedis 6.0.0, all FT.SEARCH and FT.AGGREGATE commands automatically append DIALECT 2 unless explicitly configured otherwise. This may affect query results if you were relying on DIALECT 1 behavior.

Migration Path

Option 1: Accept DIALECT 2 (Recommended)

Review your search queries to ensure they work correctly with DIALECT 2. Most queries should work without changes.

Option 2: Revert to DIALECT 1

If you need to maintain DIALECT 1 behavior:

java
JedisPooled jedis = new JedisPooled("redis://localhost:6379");

// Set default dialect to 1
jedis.setDefaultSearchDialect(1);

// Now all search commands will use DIALECT 1
SearchResult result = jedis.ftSearch("idx:products", "@category:electronics");

FT.PROFILE Return Type Change

The return type of FT.PROFILE commands has changed from Map<String, Object> to a structured ProfilingInfo object.

Changed Methods

Before (v5.x):

java
Map.Entry<SearchResult, Map<String, Object>> ftProfileSearch(
    String indexName, FTProfileParams profileParams, Query query);

Map.Entry<AggregationResult, Map<String, Object>> ftProfileAggregate(
    String indexName, FTProfileParams profileParams, AggregationBuilder aggr);

After (v6.0.0):

java
Map.Entry<SearchResult, ProfilingInfo> ftProfileSearch(
    String indexName, FTProfileParams profileParams, Query query);

Map.Entry<AggregationResult, ProfilingInfo> ftProfileAggregate(
    String indexName, FTProfileParams profileParams, AggregationBuilder aggr);

COMMAND INFO Response Changes

The response format for COMMAND INFO has been updated to include subcommand details, making it compatible with Redis 7.0+ and Redis 8.0.

Impact

If you were parsing the COMMAND INFO response, you may need to update your code to handle the new structure that includes subcommand information.

Before (v5.x):

java
List<Object> commandInfo = jedis.commandInfo("SET");
// Returns basic command information

After (v6.0.0):

java
List<Object> commandInfo = jedis.commandInfo("SET");
// Returns command information including subcommand details
// Compatible with Redis 7.0+ format

New Features

Redis 8.0 Support

Jedis 6.0.0 adds full support for Redis 8.0, which includes built-in support for:

  • JSON - Native JSON data type (previously RedisJSON module)
  • Search and Query - Full-text search capabilities (previously RediSearch module)
  • TimeSeries - Time-series data support (previously RedisTimeSeries module)

Example:

java
JedisPooled jedis = new JedisPooled("redis://localhost:6379");

// JSON operations (built-in in Redis 8.0)
jedis.jsonSet("user:1", Path2.of("$"), "{\"name\":\"John\",\"age\":30}");
String json = jedis.jsonGet("user:1");

// Search operations (built-in in Redis 8.0)
jedis.ftCreate("idx:users",
    FTCreateParams.createParams()
        .on(IndexDataType.JSON)
        .addPrefix("user:"),
    TextField.of("$.name").as("name"),
    NumericField.of("$.age").as("age"));

SearchResult result = jedis.ftSearch("idx:users", "@name:John");

SslOptions for Advanced SSL Configuration

A new SslOptions class provides advanced SSL/TLS configuration options for secure connections.

Features:

  • Custom keystore and truststore configuration
  • SSL protocol selection
  • SSL verification mode control
  • SSL parameters customization

Example:

java
SslOptions sslOptions = SslOptions.builder()
    .keystore(new File("/path/to/keystore.jks"))
    .keystorePassword("keystorePassword".toCharArray())
    .truststore(new File("/path/to/truststore.jks"))
    .truststorePassword("truststorePassword".toCharArray())
    .sslVerifyMode(SslVerifyMode.FULL)
    .build();

JedisClientConfig config = DefaultJedisClientConfig.builder()
    .ssl(true)
    .sslOptions(sslOptions)
    .build();

JedisPooled jedis = new JedisPooled("localhost", 6379, config);

Token-Based Authentication

Jedis 6.0.0 introduces support for token-based authentication, useful for cloud environments and managed Redis services.

Example:

java
// Token-based authentication with automatic token refresh
TokenCredentials tokenCredentials = new TokenCredentials("initial-token");

JedisClientConfig config = DefaultJedisClientConfig.builder()
    .credentials(tokenCredentials)
    .build();

JedisPooled jedis = new JedisPooled("localhost", 6379, config);

// Token can be updated dynamically
tokenCredentials.updateToken("new-token");

New Hash Commands

Support for new hash field expiration commands introduced in Redis 7.4:

  • HGETDEL - Get and delete a hash field
  • HGETEX - Get a hash field with expiration options
  • HSETEX - Set a hash field with expiration

Example:

java
// HSETEX - Set field with expiration
jedis.hsetex("user:1", 3600, "session", "abc123");

// HGETEX - Get field and update expiration
String session = jedis.hgetex("user:1", "session", 
    HGetExParams.hgetExParams().ex(7200));

// HGETDEL - Get and delete field
String oldSession = jedis.hgetdel("user:1", "session");

Search Warning Messages

Search and aggregation queries now support warning messages in results, helping identify potential issues with queries.

Example:

java
SearchResult result = jedis.ftSearch("idx:products", "@name:laptop");

// Check for warnings
if (result.hasWarnings()) {
    List<String> warnings = result.getWarnings();
    warnings.forEach(warning -> 
        System.out.println("Search warning: " + warning));
}

Additional Resources

Getting Help

If you encounter issues during migration:

  1. Check the Jedis GitHub Issues
  2. Join the Redis Discord
  3. Review the Jedis Javadocs
  4. Start a Discussion

Migration Guides V6 To V7 (`docs/migration-guides/v6-to-v7.md`)

Jedis 7.0.0 Migration Guide

This guide helps you migrate from Jedis 6.2.0 to Jedis 7.0.0. Version 7.0.0 includes several breaking changes focused on removing deprecated features and improving the API design.

Table of Contents

Overview

Jedis 7.0.0 is a major release that removes previously deprecated features and modernizes the API. The main focus areas are:

  1. Removal of deprecated sharding features - JedisSharding and related classes have been removed
  2. Base class consolidation - Pipeline and Transaction base classes have been renamed
  3. Builder pattern introduction - New fluent builders for JedisPooled, JedisCluster, and JedisSentineled
  4. API cleanup - Removal of deprecated constructors and methods

Breaking Changes

Removed Deprecated Sharding Features

The following classes and features have been completely removed in Jedis 7.0.0:

Removed Classes

  • JedisSharding - Use JedisCluster for distributed Redis deployments instead
  • ShardedPipeline - Use Pipeline with JedisCluster instead
  • ShardedConnectionProvider - No replacement needed
  • ShardedCommandArguments - Internal class, no replacement needed
  • ShardedCommandObjects - Internal class, no replacement needed

Migration Path

If you were using JedisSharding:

Before (v6.2.0):

java
List<HostAndPort> shards = Arrays.asList(
    new HostAndPort("localhost", 6379),
    new HostAndPort("localhost", 6380)
);
JedisSharding jedisSharding = new JedisSharding(shards);
jedisSharding.set("key", "value");

After (v7.0.0):

java
// Option 1: Use JedisCluster for distributed deployments
Set<HostAndPort> nodes = new HashSet<>(Arrays.asList(
    new HostAndPort("localhost", 6379),
    new HostAndPort("localhost", 6380)
));
JedisCluster jedisCluster = new JedisCluster(nodes);
jedisCluster.set("key", "value");

// Option 2: Use JedisPooled for single-node deployments
JedisPooled jedis = new JedisPooled("localhost", 6379);
jedis.set("key", "value");

Base Class Changes

Several base classes have been renamed to better reflect their purpose:

Pipeline Base Class

  • PipelineBase has been removed
  • Pipeline now extends AbstractPipeline instead

Impact: If you were using PipelineBase as a type reference, change it to AbstractPipeline.

Before (v6.2.0):

java
PipelineBase pipeline = jedis.pipelined();

After (v7.0.0):

java
AbstractPipeline pipeline = jedis.pipelined();
// Or use the concrete type:
Pipeline pipeline = (Pipeline) jedis.pipelined();

Transaction Base Class

  • TransactionBase has been removed
  • Transaction now extends AbstractTransaction instead

Impact: If you were using TransactionBase as a type reference, change it to AbstractTransaction.

Before (v6.2.0):

java
TransactionBase transaction = jedis.multi();

After (v7.0.0):

java
AbstractTransaction transaction = jedis.multi();
// Or use the concrete type:
Transaction transaction = (Transaction) jedis.multi();

UnifiedJedis Constructor Changes

Several deprecated constructors have been removed from UnifiedJedis:

Removed Constructors

  1. Cluster constructors with maxAttempts:

    java
    // REMOVED in v7.0.0
    UnifiedJedis(Set<HostAndPort> nodes, JedisClientConfig config, int maxAttempts)
    UnifiedJedis(Set<HostAndPort> nodes, JedisClientConfig config, int maxAttempts, Duration maxTotalRetriesDuration)
    UnifiedJedis(Set<HostAndPort> nodes, JedisClientConfig config, GenericObjectPoolConfig<Connection> poolConfig, int maxAttempts, Duration maxTotalRetriesDuration)
  2. Sharding constructors:

    java
    // REMOVED in v7.0.0
    UnifiedJedis(ShardedConnectionProvider provider)
    UnifiedJedis(ShardedConnectionProvider provider, Pattern tagPattern)

Migration Path

Before (v6.2.0):

java
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("localhost", 6379));
JedisClientConfig config = DefaultJedisClientConfig.builder().build();

UnifiedJedis jedis = new UnifiedJedis(nodes, config, 3);

After (v7.0.0):

java
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("localhost", 6379));
JedisClientConfig config = DefaultJedisClientConfig.builder().build();

// Use JedisCluster instead
JedisCluster jedis = new JedisCluster(nodes, config);

// Or use the new builder pattern
JedisCluster jedis = JedisCluster.builder()
    .nodes(nodes)
    .clientConfig(config)
    .build();

Return Type Changes

UnifiedJedis.pipelined()

The return type has been changed to be more generic:

Before (v6.2.0):

java
PipelineBase pipelined()

After (v7.0.0):

java
AbstractPipeline pipelined()

Impact: Minimal - AbstractPipeline is the parent class, so existing code should continue to work.

New Features

Automatic Failover and Failback

Jedis 7.0.0 significantly refactors the automatic failover and failback API. If you were using the failover features in v6.2.0 with MultiClusterClientConfig and MultiClusterPooledConnectionProvider, these have been renamed and improved in v7.0.0.

For detailed migration guidance on automatic failover and failback please refer to the Automatic Failover and Failback Migration Guide.

Builder Pattern for Client Creation

Jedis 7.0.0 introduces a fluent builder pattern for creating client instances, making configuration more intuitive and discoverable.

JedisPooled Builder

New in v7.0.0:

java
JedisPooled jedis = JedisPooled.builder()
    .hostAndPort("localhost", 6379)
    .clientConfig(DefaultJedisClientConfig.builder()
        .user("myuser")
        .password("mypassword")
        .database(0)
        .build())
    .poolConfig(new GenericObjectPoolConfig<>())
    .build();

JedisCluster Builder

New in v7.0.0:

java
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("localhost", 7000));
nodes.add(new HostAndPort("localhost", 7001));

JedisCluster cluster = JedisCluster.builder()
    .nodes(nodes)
    .clientConfig(DefaultJedisClientConfig.builder()
        .password("mypassword")
        .build())
    .maxAttempts(5)
    .maxTotalRetriesDuration(Duration.ofSeconds(10))
    .build();

JedisSentineled Builder

New in v7.0.0:

java
Set<HostAndPort> sentinels = new HashSet<>();
sentinels.add(new HostAndPort("localhost", 26379));
sentinels.add(new HostAndPort("localhost", 26380));

JedisSentineled jedis = JedisSentineled.builder()
    .masterName("mymaster")
    .sentinels(sentinels)
    .clientConfig(DefaultJedisClientConfig.builder()
        .password("mypassword")
        .build())
    .build();

Getting Help

If you encounter issues during migration create an issue or start a discussion.


Migration Guides V7 To V8 (`docs/migration-guides/v7-to-v8.md`)

Jedis 8.0.0 Migration Guide

This guide helps you migrate from Jedis 7.5.0 to Jedis 8.0.0. Version 8.0.0 introduces several breaking changes focused on completing the client class consolidation, modernizing protocol defaults, and tightening security defaults.

Table of Contents

Overview

Jedis 8.0.0 is a major release that finishes the client-class consolidation started in 7.x and modernizes a few long-standing defaults. The main focus areas are:

  1. RESP3 auto-negotiation on by default for all UnifiedJedis-based clients (with graceful RESP2 fallback)
  2. Final removal of legacy JedisPooled / JedisSentineled classes in favor of the RedisClient family introduced in 7.0.0
  3. Removal of deprecated UnifiedJedis public constructors — clients are now created exclusively through the dedicated client classes or their builders
  4. Internal refactoring that affects users of low-level extension points (custom CommandExecutor, Transaction subclasses, CommandObjects)

Breaking Changes

RESP3 Negotiated by Default

UnifiedJedis-based clients (RedisClient, RedisClusterClient, RedisSentinelClient, MultiDbClient, and the deprecated JedisCluster) now auto-negotiate the RESP3 protocol by default.

The negotiation logic:

  1. When JedisClientConfig.getRedisProtocol() == null and isAutoNegotiateProtocol() == true (the new default), the client sends HELLO 3 on connect and gracefully falls back to RESP2 if the server rejects it.
  2. When getRedisProtocol() is RESP2 or RESP3, that version is enforced strictly — the connection fails if the server rejects it.
  3. When getRedisProtocol() == null and isAutoNegotiateProtocol() == false, the legacy "no HELLO" behavior is preserved (RESP2 assumed on the wire).

The legacy Jedis class doesn't fully support RESP3 protocol format and therefore ignores the auto-negotiation flag; it emits a warning if it is left enabled. To suppress the warning in Jedis-based code paths, use the new serverDefaultProtocol() shortcut:

java
JedisClientConfig config = DefaultJedisClientConfig.builder()
    .serverDefaultProtocol()   // equivalent to .protocol(null).autoNegotiateProtocol(false)
    .build();
Jedis jedis = new Jedis(host, port, config);

New `JedisClientConfig` API

java
public interface JedisClientConfig {
    // ...existing...
    default boolean isAutoNegotiateProtocol() { return true; }  // NEW, default = true
}
java
// New builder methods on DefaultJedisClientConfig.Builder
Builder resp2();                              // shortcut for protocol(RESP2)
Builder serverDefaultProtocol();              // legacy "no HELLO" mode
Builder autoNegotiateProtocol(boolean value);

If your application relied on RESP2 reply shapes for commands whose RESP3 shape differs (e.g. HRANDFIELD WITHVALUES, JSON.TYPE, XREAD/XREADGROUP, certain RediSearch shapes), pin the protocol explicitly:

java
JedisClientConfig config = DefaultJedisClientConfig.builder().resp2().build();
RedisClient client = RedisClient.builder().clientConfig(config).build();
  • JSON.NUMINCRBY now correctly returns the original numeric type (long or double) on RESP3 instead of always returning a double.
  • RediSearch NOCONTENT responses now return an empty map on RESP3 instead of throwing on field access; missing scores default to 0.0 and missing warnings default to an empty list on both protocols.
  • The deprecated setProtocol(RedisProtocol) mutator on Jedis was removed as part of the same change set.

TLS Hostname Verification Enforced by Default

DefaultJedisSocketFactory now sets SSLParameters.setEndpointIdentificationAlgorithm("HTTPS") whenever SSL is enabled and the user has not supplied custom SSLParameters. Connections to a TLS endpoint whose certificate CN/SAN does not match the host you connect to will now fail with an SSL handshake error.

This is a security-positive change but may surface previously silent misconfigurations: self-signed certificates without a matching SAN, hostname/IP mismatches between client and certificate, or wildcard-SAN limits will now reject the connection instead of silently accepting it.

Migration Path

The recommended migration is to fix the certificate (add a SAN that matches the connection host) or to use the modern SslOptions API, which exposes a clear SslVerifyMode:

java
SslOptions sslOptions = SslOptions.builder()
    .truststore(trustStore)
    .sslVerifyMode(SslVerifyMode.FULL)   // hostname verification on
    .build();
JedisClientConfig config = DefaultJedisClientConfig.builder()
    .sslOptions(sslOptions)
    .build();

If you intentionally need to bypass hostname verification (not recommended for production), supply explicit SSLParameters with no endpoint identification algorithm:

java
SSLParameters sslParameters = new SSLParameters();
// leave endpointIdentificationAlgorithm null/empty to disable verification
JedisClientConfig config = DefaultJedisClientConfig.builder()
    .ssl(true)
    .sslParameters(sslParameters)
    .build();

Note: the legacy ssl(), sslSocketFactory(), sslParameters(), and hostnameVerifier() setters used in the second snippet are themselves deprecated — see Legacy SSL Configuration Setters Deprecated.

Removed Client Classes (`JedisPooled`, `JedisSentineled`)

JedisPooled and JedisSentineled were deprecated in 7.0.0 and have now been completely removed. Use the corresponding classes from the RedisClient family introduced in 7.0.0.

v7.5.0 v8.0.0
redis.clients.jedis.JedisPooled redis.clients.jedis.RedisClient
redis.clients.jedis.JedisSentineled redis.clients.jedis.RedisSentinelClient

Migration Path

Before (v7.5.0):

java
// Standalone
JedisPooled jedis = new JedisPooled("localhost", 6379);
jedis.set("key", "value");

// Sentinel
JedisSentineled sentinel = new JedisSentineled(
    "mymaster", masterConfig, sentinels, sentinelConfig);

After (v8.0.0):

java
// Standalone — simple factory
RedisClient jedis = RedisClient.create("localhost", 6379);
jedis.set("key", "value");

// Standalone — builder for advanced configuration
RedisClient jedis = RedisClient.builder()
    .hostAndPort("localhost", 6379)
    .clientConfig(DefaultJedisClientConfig.builder()
        .user("myuser")
        .password("mypassword")
        .build())
    .build();

// Sentinel — builder
RedisSentinelClient sentinel = RedisSentinelClient.builder()
    .masterName("mymaster")
    .sentinels(sentinels)
    .clientConfig(masterConfig)
    .sentinelClientConfig(sentinelConfig)
    .build();

Removed `UnifiedJedis` Public Constructors

All previously deprecated public constructors on UnifiedJedis have been removed. The class is no longer intended to be instantiated directly by users — use RedisClient, RedisClusterClient, RedisSentinelClient, or MultiDbClient (and their builders) instead.

Removed constructors

java
// REMOVED in v8.0.0
UnifiedJedis()
UnifiedJedis(HostAndPort hostAndPort)
UnifiedJedis(String url)
UnifiedJedis(URI uri)
UnifiedJedis(URI uri, JedisClientConfig config)
UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig)
UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig, CacheConfig cacheConfig)
UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig, Cache cache)
UnifiedJedis(ConnectionProvider provider)
UnifiedJedis(JedisSocketFactory socketFactory)
UnifiedJedis(JedisSocketFactory socketFactory, JedisClientConfig clientConfig)
UnifiedJedis(CommandExecutor executor)

The following constructor changed visibility from public to protected:

java
// Now protected (still callable from subclasses)
UnifiedJedis(Connection connection)

The following constructor is not removed, but is now @Deprecated — call sites continue to compile (the wider ConnectionProvider signature absorbs former ClusterConnectionProvider callers), but new code should use RedisClusterClient.builder():

java
@Deprecated
public UnifiedJedis(ConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration)

Migration Path

Before (v7.5.0):

java
UnifiedJedis jedis = new UnifiedJedis(new HostAndPort("localhost", 6379));
UnifiedJedis jedis2 = new UnifiedJedis(URI.create("redis://localhost:6379"));
UnifiedJedis custom = new UnifiedJedis(myConnectionProvider);

After (v8.0.0):

java
RedisClient jedis = RedisClient.create(new HostAndPort("localhost", 6379));
RedisClient jedis2 = RedisClient.create("redis://localhost:6379");
RedisClient custom = RedisClient.builder()
    .connectionProvider(myConnectionProvider)
    .build();

`Transaction(Jedis)` and `ClusterPipeline(ClusterConnectionProvider)` Constructors Removed

`Transaction(Jedis)`

The legacy public Transaction(Jedis jedis) constructor — which Jedis used internally to wire resetState() callbacks — has been removed. The coupling is now handled inside Jedis.multi() via the new onAfterExec() / onAfterDiscard() hook methods.

If you subclassed Transaction or constructed it manually with a Jedis reference, switch to the Connection-based constructor and override the hooks if you need exec/discard notifications.

Before (v7.5.0):

java
public Transaction(Jedis jedis); // public, took a Jedis

After (v8.0.0):

java
public Transaction(Connection connection);            // existing
public Transaction(Connection connection, boolean doMulti);
public Transaction(Connection connection, boolean doMulti, boolean closeConnection);

// New protected hooks to override:
protected void onAfterExec();
protected void onAfterDiscard();

Additionally, the deprecated protected AbstractTransaction() no-arg constructor has been removed — subclasses must call AbstractTransaction(CommandObjects commandObjects).

`ClusterPipeline(ClusterConnectionProvider)`

The single-argument public ClusterPipeline(ClusterConnectionProvider provider) constructor has been removed. It silently constructed a ClusterCommandObjects with no protocol information, which is no longer valid now that CommandObjects requires an explicit protocol (see CommandObjects Protocol is Now Immutable).

A new overload accepts the protocol explicitly:

Before (v7.5.0):

java
new ClusterPipeline(provider);

After (v8.0.0):

java
new ClusterPipeline(provider, RedisProtocol.RESP3);    // or RESP2
// or, if you already have a ClusterCommandObjects:
new ClusterPipeline(provider, clusterCommandObjects);  // unchanged

`CommandObjects` Protocol is Now Immutable

The internal CommandObjects class no longer allows mutating the protocol after construction:

  • The no-arg public CommandObjects() constructor has been removed.
  • The public final void setProtocol(RedisProtocol) setter has been removed.
  • public CommandObjects(RedisProtocol protocol) now throws IllegalArgumentException if protocol is null — use RedisProtocol.orServerDefault(protocol) (which falls back to RESP2) when you do not know the negotiated protocol up front.

This only affects code that integrates with Jedis internals (custom UnifiedJedis subclasses, custom CommandExecutor, instrumentation). Application code that uses the RedisClient* builders is unaffected.

Before (v7.5.0):

java
CommandObjects commandObjects = new CommandObjects();
// ... later
commandObjects.setProtocol(RedisProtocol.RESP3);

After (v8.0.0):

java
CommandObjects commandObjects = new CommandObjects(RedisProtocol.orServerDefault(protocol));

A new constant RedisProtocol.REDIS_SERVER_DEFAULT_PROTO (= RESP2) and a helper RedisProtocol.orServerDefault(RedisProtocol) are provided for this purpose.

`broadcastCommand` and `JedisBroadcastAndRoundRobinConfig` Removed

Broadcast routing is no longer a separate API surface — it is now driven entirely by the new request/response policy registry (see Request/Response Policy Support for Cluster Commands). The following has been removed:

  • The JedisBroadcastAndRoundRobinConfig interface (including the RediSearchMode.DEFAULT / RediSearchMode.LIGHT enum) is deleted.
  • UnifiedJedis.setBroadcastAndRoundRobinConfig(JedisBroadcastAndRoundRobinConfig) is removed.
  • The default broadcastCommand(CommandObject<T>) method on the CommandExecutor interface is removed.
  • UnifiedJedis.broadcastCommand(...) is removed — broadcast routing now flows through executeCommand(...) and is resolved by the registry.

Migration Path

In v7.x, users running RediSearch against Redis 7 (where FT.CREATE only created the index on the contacted shard) had to opt into broadcasting indexes manually:

java
// v7.5.0 — manual broadcast opt-in for cluster + RediSearch
JedisBroadcastAndRoundRobinConfig cfg = () -> RediSearchMode.DEFAULT;
jedis.setBroadcastAndRoundRobinConfig(cfg);
jedis.ftCreate("idx", IndexOptions.defaultOptions(), schema);

This is no longer necessary in v8.0.0. Redis 8.0+ broadcasts FT.CREATE automatically on the server side as well, so the index is created cluster-wide regardless. Simply drop the configuration:

java
// v8.0.0 — no special configuration required
jedis.ftCreate("idx", IndexOptions.defaultOptions(), schema);

Custom CommandExecutor implementations that overrode broadcastCommand should remove the override.

Removed Sharding Utility Classes

The following utility classes — leftovers from the JedisSharding feature removed in 7.0.0 — have been deleted:

  • redis.clients.jedis.util.Hashing
  • redis.clients.jedis.util.MurmurHash

If you happened to use these utility classes for application-level hashing, copy them into your own codebase or switch to an equivalent library (Guava's Hashing.murmur3_*, etc.).

`aclLogBinary()` Removed

Jedis.aclLogBinary() and aclLogBinary(int) (declared on AccessControlLogBinaryCommands) have been removed. Their declared return type List could not represent the ACL LOG reply, which is a nested array (a list of entries, each a flattened list of key/value pairs). Reading any element threw ClassCastException, so the methods were never usable.

Migration Path

Use aclLog() / aclLog(int), which return structured AccessControlLogEntry objects for the same data:

java
List<AccessControlLogEntry> log = jedis.aclLog();

`commons-pool2` Upgraded to 2.13.1

The org.apache.commons:commons-pool2 dependency has been bumped from 2.12.1 to 2.13.1. The connection-close exception handling inside MultiDbCommandExecutor was adjusted accordingly. If you pin commons-pool2 directly in your pom.xml / build.gradle, update it to 2.13.1 to stay consistent with what Jedis is tested against.

Deprecations

`JedisCluster` Deprecation

JedisCluster is now @Deprecated in favor of RedisClusterClient. The class still works, but new code should use RedisClusterClient.

Before (v7.5.0):

java
JedisCluster cluster = new JedisCluster(nodes, clientConfig);

After (v8.0.0):

java
RedisClusterClient cluster = RedisClusterClient.builder()
    .nodes(nodes)
    .clientConfig(clientConfig)
    .build();

Legacy SSL Configuration Setters Deprecated

The legacy SSL configuration surface on JedisClientConfig and DefaultJedisClientConfig.Builder is now @Deprecated (since 7.4.2) in favor of SslOptions:

java
// Deprecated on JedisClientConfig:
SSLSocketFactory getSslSocketFactory();
SSLParameters   getSslParameters();
// (HostnameVerifier was already deprecated)

// And the corresponding builder setters:
DefaultJedisClientConfig.Builder.ssl(boolean);
DefaultJedisClientConfig.Builder.sslSocketFactory(SSLSocketFactory);
DefaultJedisClientConfig.Builder.sslParameters(SSLParameters);
DefaultJedisClientConfig.Builder.hostnameVerifier(HostnameVerifier);

SslOptions is the single configuration knob going forward. It bundles trust store, key store, verify mode, and protocol selection in one place, and takes precedence over the legacy setters when both are present:

Before (v7.5.0):

java
JedisClientConfig config = DefaultJedisClientConfig.builder()
    .ssl(true)
    .sslSocketFactory(mySocketFactory)
    .sslParameters(myParameters)
    .hostnameVerifier(myVerifier)
    .build();

After (v8.0.0):

java
SslOptions sslOptions = SslOptions.builder()
    .truststore(trustStoreFile, trustStorePassword)
    .keystore(keyStoreFile, keyStorePassword)
    .sslVerifyMode(SslVerifyMode.FULL)
    .build();
JedisClientConfig config = DefaultJedisClientConfig.builder()
    .sslOptions(sslOptions)
    .build();

RediSearch Cursor APIs Deprecated

The low-level cursor methods on RediSearchCommands are deprecated because they don't work reliably on cluster deployments (the cursor lives on a specific shard, but the next command may be routed elsewhere):

java
// Deprecated in v8.0.0
AggregationResult ftCursorRead(String indexName, long cursorId, int count);
String           ftCursorDel(String indexName, long cursorId);

The companion search-side iterator class is deprecated for the same reasons:

java
// Deprecated in v8.0.0
redis.clients.jedis.search.FtSearchIteration

For FT.SEARCH, call UnifiedJedis#ftSearch(String, String, FTSearchParams) directly and paginate via FTSearchParams.limit(offset, count). For FT.AGGREGATE, use the new ftAggregateIterator(...) method, which encapsulates cursor lifecycle and connection affinity:

Before (v7.5.0):

java
AggregationBuilder b = new AggregationBuilder("*").cursor(100);
AggregationResult page = jedis.ftAggregate(idx, b);
while (page.getCursorId() != 0) {
    page = jedis.ftCursorRead(idx, page.getCursorId(), 100);
}

After (v8.0.0):

java
AggregationBuilder b = new AggregationBuilder("*").cursor(100);
try (AggregateIterator it = jedis.ftAggregateIterator(idx, b)) {
    while (it.hasNext()) {
        AggregationResult page = it.next();
        // process page
    }
}

AggregationResult also gains an isEmpty() convenience method.

New Features

Request/Response Policy Support for Cluster Commands

Jedis 8.0.0 introduces a comprehensive request/response policy framework that aligns with the Redis command tips — every command now declares how it should be routed (single shard, all shards, all masters, keyless) and how multi-shard responses should be aggregated (logical-AND, sum, set-union, etc.).

Highlights:

  • New CommandFlagsRegistry interface — every command now exposes its request/response policy metadata.
  • CommandArguments.isKeyless() to inspect routing intent.
  • New exceptions: ClusterAggregationException, UnsupportedAggregationException.

For most users this is transparent — cluster commands that previously needed manual fan-out (INFO, CONFIG SET, DBSIZE, etc.) now work correctly out of the box on RedisClusterClient.

Out-of-Band Push Notification Handling

Connection and Protocol now understand out-of-band RESP3 push messages and route them through a configurable PushConsumerChain. Unsupported push types are silently dropped instead of failing the connection. This makes it safe to enable features such as CLIENT TRACKING on regular connections — invalidation pushes are no longer treated as protocol errors.

New (experimental) APIs:

java
@Experimental public interface PushConsumer {
    PushConsumerContext handle(PushConsumerContext context);
}
@Experimental public interface PushConsumerChain { /* ... */ }
@Experimental public final class PushMessage { /* ... */ }
@Experimental public final class PushMessageTypes { /* ... */ }

Cursor-Based `FT.AGGREGATE` Iterator

See RediSearch Cursor APIs Deprecated. The new AggregateIterator (returned by ftAggregateIterator(...)) is cluster-safe and implements AutoCloseable, so it can be used in a try-with-resources block.

`CONFIG GET`/`SET` on `UnifiedJedis`

UnifiedJedis (and therefore all RedisClient* clients) now exposes:

java
public Map<String, String> configGet(String pattern);
public Map<String, String> configGet(String... patterns);
public String configSet(Map<String, String> parameterValues);

On RedisClusterClient, configGet is routed to a random node (configuration is assumed to be uniform across the cluster), while configSet fans out to all masters so the change is applied cluster-wide (see the request/response policy framework).

Shared Executor for `ClusterPipeline`

JedisCluster.pipelined() / RedisClusterClient.pipelined() previously created and tore down a private ExecutorService for every pipeline, which was wasteful for short pipelines under load. A new overload accepts a user-supplied executor:

java
public ClusterPipeline pipelined(ExecutorService executorService);

Pass a shared executor when you create many short pipelines; reuse semantics are documented on the method.

Pool Access on `RedisSentinelClient`

RedisSentinelClient now exposes the underlying primary-node connection pools (issue #4469):

java
public Map<?, Pool<Connection>> getPrimaryNodesConnectionMap();

The returned map is keyed by master HostAndPort and yields the Pool<Connection> backing each primary, allowing instrumentation, draining, and pool-size monitoring without subclassing the provider.

Getting Help

If you encounter issues during migration create an issue or start a discussion.


Release Notes 8.1.0 (`docs/release-notes/8.1.0.md`)

Jedis 8.1.0 Release Notes

This guide helps you upgrade from Jedis 8.0.0.

!!! warning "Unreleased"
Jedis 8.1.0 has not been released yet. These notes track changes on master
since 8.0.0 and are updated as changes merge; remove this notice when 8.1.0
ships.

!!! note
If your change alters user-visible behavior, add an entry here in the same PR.

Highlights

Smart Client Handoffs ([#4668](https://github.com/redis/jedis/pull/4668))

Jedis now reacts to server maintenance push notifications with relaxed timeouts and pre-handoffs.
The feature requires RESP3 and is configured with MaintenanceNotificationsConfig via
RedisClient.builder().maintenanceNotifications(...). It is enabled by default (Mode.AUTO) on
RESP3 connections; pass MaintenanceNotificationsConfig.DISABLED to opt out. See the
Smart Client Handoffs guide.

Capacity hints for collection-built commands ([#4737](https://github.com/redis/jedis/pull/4737))

CommandArguments gained ensureCapacity(int), a chainable hint that pre-sizes the backing
argument list for the expected total number of arguments (command token included — the RESP
array length of the final command):

java
CommandArguments args = new CommandArguments(ZADD)
    .ensureCapacity(2 + 2 * members.size()) // command + key + member/score pairs
    .key(key);

This avoids repeated ArrayList growth copies when large commands are built directly, e.g.
via executeCommand. Under-estimating falls back to normal growth, so correctness never
depends on the estimate. The typed API benefits automatically: collection-built commands
(MSET, map HSET/HMSET, ZADD, GEOADD, XADD, addObjects(...)) now pre-size
internally from the known collection size.

API Additions

Vector set plain overloads ([#4693](https://github.com/redis/jedis/pull/4693))

Parameter-free overloads for the experimental vector-set methods that previously required null
params: vadd/vaddFP32 with reduceDim, and the vsim*WithScores and vsim*WithScoresAndAttribs
variants, across the String, binary, and pipeline APIs.

Behavior Changes

Changes that may affect existing applications even though they are
not breaking API changes.

Closing a client no longer stops its `AuthXManager`

An AuthXManager is created by the application and passed in through JedisClientConfig, so its
lifecycle now belongs to the application, like the config itself. Previously ConnectionPool.close()
stopped the manager unconditionally, which broke every other user of a shared manager: after a
Sentinel master failover the new master pool was left without token renewal, and closing one of
several clients built on the same manager stopped renewal for the rest. Cluster and multi-db clients,
on the other hand, never stopped it. All clients now behave the same and leave the manager running.

Closed and destroyed pools are also properly unregistered from the manager. Previously the
unregistration silently failed, so a stale pool kept receiving post-authentication callbacks and
made the token manager's renewal cycle fail on its closed pool.

Action required: call authXManager.stop() after closing the last client that uses it.
Applications that relied on RedisClient.close(), JedisPooled.close() or
RedisSentinelClient.close() to stop the manager will otherwise keep its renewal threads alive.

Key pre-processor applied to all key arguments ([#4675](https://github.com/redis/jedis/pull/4675))

The source key of ZRANGESTORE/GEOSEARCHSTORE and the script KEYS passed to
eval(script, keyCount, ...) / evalsha(sha1, keyCount, ...) were previously not
treated as keys: a configured CommandKeyArgumentPreProcessor (e.g. key prefixing)
did not transform them, and the store commands' source key was invisible to cluster
key routing. They are now handled like every other key argument: the pre-processor
applies to them, and on cluster a cross-slot ZRANGESTORE/GEOSEARCHSTORE fails
client-side with JedisClusterOperationException instead of the server's CROSSSLOT
error.

Action required: remove any manual prefixing workaround for these arguments
(they would now be prefixed twice), and update catch blocks that matched the
server CROSSSLOT JedisDataException for these commands.

Pub/sub push messages are no longer delivered as command replies outside a subscription ([#4701](https://github.com/redis/jedis/pull/4701))

On RESP3, a pub/sub push frame that arrived while no pub/sub read loop was running —
for example a message the server delivered after the UNSUBSCRIBE confirmation,
as can happen behind the Redis Enterprise proxy — was returned as the next command's
reply. This desynced the reply stream and typically surfaced as
ClassCastException: java.util.ArrayList cannot be cast to [B.

Such stray pub/sub pushes are now logged at debug level and dropped instead of being
delivered to the application, and the following command receives its own reply.
Behavior during an active subscribe/psubscribe/ssubscribe loop is unchanged.
Custom PushConsumers registered on a connection now also observe pub/sub-typed
pushes received outside a subscription, which the built-in consumer previously
propagated before they could see them; the public PUBSUB_CONSUMER constant is
unchanged and still propagates unconditionally.

Action required: none for typical applications. Code that relied on reading raw
pub/sub frames from a connection without going through the JedisPubSub/
JedisShardedPubSub loops (e.g. hand-rolled sendCommand(SUBSCRIBE) +
getUnflushedObject()) will no longer receive those frames; use the pub/sub APIs or
register a custom PushConsumer that propagates them.


--- METRICS ---