title: Lettuce guide (Java)

summary: Connect your Lettuce application to a Redis database

title: Connect to the server

title: Connect to Azure Managed Redis

summary: Learn how to use Redis Search with JSON and hash documents.

title: Index and query documents

summary: Learn how to index and query vector embeddings with Redis

title: Index and query vectors

title: Vector set embeddings

title: Client-side geographic failover

summary: Get your Lettuce app ready for production

title: Production usage

title: Install

text:
To include Lettuce as a dependency in your application, edit the appropriate dependency file as shown below.

If you use Maven, add the following dependency to your `pom.xml`:

[code example]

If you use Gradle, include this line in your `build.gradle` file:

[code example]

If you wish to use the JAR files directly, download the latest Lettuce and, optionally, Apache Commons Pool2 JAR files from Maven Central or any other Maven repository.

To build from source, see the instructions on the [Lettuce source code GitHub repo](https://github.com/lettuce-io/lettuce-core).

title: Connect and test

text:
Connect to a local server using the following code. First, import
the required classes.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C, C# (Asynchronous), C# (Synchronous), Go, Java, Java (Synchronous - Jedis), JavaScript (Node.js), JavaScript (Node.js), PHP, Python, Ruby, Rust (Asynchronous), Rust (Synchronous)

**C:**

[code example]

**C# (Asynchronous):**

[code example]

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**PHP:**

[code example]

**Python:**

[code example]

**Ruby:**

[code example]

**Rust (Asynchronous):**

[code example]

**Rust (Synchronous):**

[code example]

Use the following code to connect to the server.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C, C# (Asynchronous), C# (Synchronous), Go, Java, Java (Synchronous - Jedis), JavaScript (Node.js), JavaScript (Node.js), PHP, Python, Ruby, Rust (Asynchronous), Rust (Synchronous)

**C:**

[code example]

**C# (Asynchronous):**

[code example]

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**PHP:**

[code example]

**Python:**

[code example]

**Ruby:**

[code example]

**Rust (Asynchronous):**

[code example]

**Rust (Synchronous):**

[code example]

Test the connection by storing and retrieving a simple string.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C, C# (Asynchronous), C# (Synchronous), Go, Java, Java (Synchronous - Jedis), JavaScript (Node.js), JavaScript (Node.js), PHP, Python, Ruby, Rust (Asynchronous), Rust (Synchronous)

**C:**

[code example]

**C# (Asynchronous):**

[code example]

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**PHP:**

[code example]

**Python:**

[code example]

**Ruby:**

[code example]

**Rust (Asynchronous):**

[code example]

**Rust (Synchronous):**

[code example]

Close the connection when you're done.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C, C# (Asynchronous), C# (Synchronous), Go, Java, Java (Synchronous - Jedis), JavaScript (Node.js), JavaScript (Node.js), PHP, Python, Ruby, Rust (Asynchronous), Rust (Synchronous)

**C:**

[code example]

**C# (Asynchronous):**

[code example]

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**PHP:**

[code example]

**Python:**

[code example]

**Ruby:**

[code example]

**Rust (Asynchronous):**

[code example]

**Rust (Synchronous):**

[code example]

title: More information

text:
The [Lettuce reference guide](https://redis.github.io/lettuce/) has more examples
and an API reference. You may also be interested in the
[Project Reactor](https://projectreactor.io/) library that Lettuce uses.

See also the other pages in this section for more information and examples:

code:
<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>6.7.1.RELEASE</version> <!-- Check for the latest version on Maven Central -->
</dependency>

section_id: install

code:
dependencies {
    compileOnly 'io.lettuce:lettuce-core:6.7.1.RELEASE'
}

code:
#include <stdio.h>
#include <stdlib.h>

#include <hiredis/hiredis.h>

int main() {
    // The `redisContext` type represents the connection
    // to the Redis server. Here, we connect to the
    // default host and port.
    redisContext *c = redisConnect("127.0.0.1", 6379);

    // Check if the context is null or if a specific
    // error occurred.
    if (c == NULL || c->err) {
        if (c != NULL) {
            printf("Error: %s\n", c->errstr);
            // handle error
        } else {
            printf("Can't allocate redis context\n");
        }

        exit(1);
    }

    // Set a string key.
    redisReply *reply = redisCommand(c, "SET foo bar");
    printf("Reply: %s\n", reply->str); // >>> Reply: OK
    freeReplyObject(reply);

    // Get the key we have just stored.
    reply = redisCommand(c, "GET foo");
    printf("Reply: %s\n", reply->str); // >>> Reply: bar
    freeReplyObject(reply);

    // Close the connection.
    redisFree(c);
}

section_id: connect-and-test

code:
import (
	"context"
	"fmt"

	"github.com/redis/go-redis/v9"
)

code:
import io.lettuce.core.*;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;

code:
import redis.clients.jedis.RedisClient;
import java.util.HashMap;
import java.util.Map;

code:
import { createClient } from 'redis';

const client = createClient();

client.on('error', err => console.log('Redis Client Error', err));

await client.connect();

await client.set('key', 'value');
const value = await client.get('key');
console.log(value); // >>> value

await client.hSet('user-session:123', {
    name: 'John',
    surname: 'Smith',
    company: 'Redis',
    age: 29
})

let userSession = await client.hGetAll('user-session:123');
console.log(JSON.stringify(userSession, null, 2));
/* >>>
{
  "surname": "Smith",
  "name": "John",
  "company": "Redis",
  "age": "29"
}
 */

await client.quit();

code:
import { Redis } from 'ioredis';

const redis = new Redis();

await redis.set('key', 'value');
const value = await redis.get('key');
console.log(value); // >>> value

await redis.hset('user-session:123', {
    name: 'John',
    surname: 'Smith',
    company: 'Redis',
    age: 29
});

const userSession = await redis.hgetall('user-session:123');
console.log(JSON.stringify(userSession, null, 2));
/* >>>
{
  "surname": "Smith",
  "name": "John",
  "company": "Redis",
  "age": "29"
}
 */

redis.disconnect();

code:
<?php

require 'vendor/autoload.php';

use Predis\Client as PredisClient;

$r = new PredisClient([
                'scheme'   => 'tcp',
                'host'     => '127.0.0.1',
                'port'     => 6379,
                'password' => '',
                'database' => 0,
            ]);

echo $r->set('foo', 'bar'), PHP_EOL;
// >>> OK

echo $r->get('foo'), PHP_EOL;
// >>> bar

$r->hset('user-session:123', 'name', 'John');
$r->hset('user-session:123', 'surname', 'Smith');
$r->hset('user-session:123', 'company', 'Redis');
$r->hset('user-session:123', 'age', 29);

echo var_export($r->hgetall('user-session:123')), PHP_EOL;
/* >>>
array (
  'name' => 'John',
  'surname' => 'Smith',
  'company' => 'Redis',
  'age' => '29',
)
*/

code:
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)

r.set('foo', 'bar')
# True
r.get('foo')
# bar

r.hset('user-session:123', mapping={
    'name': 'John',
    "surname": 'Smith',
    "company": 'Redis',
    "age": 29
})
# True

r.hgetall('user-session:123')
# {'surname': 'Smith', 'name': 'John', 'company': 'Redis', 'age': '29'}

r.close()

code: require 'redis'

code: use redis::AsyncCommands;

code: use redis::Commands;

code:
rdb := redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "", // no password
		DB:       0,  // use default DB
		Protocol: 2,
	})

	ctx := context.Background()

code:
RedisURI uri = RedisURI.Builder
                .redis("localhost", 6379)
                .build();

        RedisClient client = RedisClient.create(uri);
        StatefulRedisConnection<String, String> connection = client.connect();
        RedisCommands<String, String> commands = connection.sync();

code: RedisClient jedis = new RedisClient("redis://localhost:6379");

code: r = Redis.new

code:
err := rdb.Set(ctx, "foo", "bar", 0).Err()
	if err != nil {
		panic(err)
	}

	val, err := rdb.Get(ctx, "foo").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("foo", val) // >>> foo bar

code:
commands.set("foo", "bar");
        String result = commands.get("foo");
        System.out.println(result); // >>> bar

code:
String res1 = jedis.set("bike:1", "Deimos");
        System.out.println(res1); // >>> OK

        String res2 = jedis.get("bike:1");
        System.out.println(res2); // >>> Deimos

code:
r.set 'foo', 'bar'
value = r.get('foo')
puts value # >>> bar

code:
using NRedisStack;
using NRedisStack.RedisStackCommands;
using StackExchange.Redis;

public class AsyncLandingExample
{
    public async Task Run()
    {
        var muxer = await ConnectionMultiplexer.ConnectAsync("localhost:6379");
        var db = muxer.GetDatabase();

        await db.StringSetAsync("foo", "bar");
        string? fooResult = await db.StringGetAsync("foo");
        Console.WriteLine(fooResult); // >>> bar

        var hash = new HashEntry[] {
            new HashEntry("name", "John"),
            new HashEntry("surname", "Smith"),
            new HashEntry("company", "Redis"),
            new HashEntry("age", "29"),
            };
        await db.HashSetAsync("user-session:123", hash);

        var hashFields = await db.HashGetAllAsync("user-session:123");
        Console.WriteLine(String.Join("; ", hashFields));
        // >>> name: John; surname: Smith; company: Redis; age: 29
    }
}

code:
using NRedisStack;
using NRedisStack.RedisStackCommands;
using StackExchange.Redis;

public class SyncLandingExample
{
    public void Run()
    {
        var muxer = ConnectionMultiplexer.Connect("localhost:6379");
        var db = muxer.GetDatabase();

        db.StringSet("foo", "bar");
        Console.WriteLine(db.StringGet("foo")); // >>> bar

        var hash = new HashEntry[] {
            new HashEntry("name", "John"),
            new HashEntry("surname", "Smith"),
            new HashEntry("company", "Redis"),
            new HashEntry("age", "29"),
        };
        db.HashSet("user-session:123", hash);

        var hashFields = db.HashGetAll("user-session:123");
        Console.WriteLine(String.Join("; ", hashFields));
        // >>> name: John; surname: Smith; company: Redis; age: 29
    }
}

code:
connection.close();
        client.shutdown();

code: await client.quit();

code:
mod tests {
    use redis::AsyncCommands;

    async fn run() {
        let mut r = match redis::Client::open("redis://127.0.0.1") {
            Ok(client) => {
                match client.get_multiplexed_async_connection().await {
                    Ok(conn) => conn,
                    Err(e) => {
                        println!("Failed to connect to Redis: {e}");
                        return;
                    }
                }
            },
            Err(e) => {
                println!("Failed to create Redis client: {e}");
                return;
            }
        };

        if let Ok(res) = r.set("foo", "bar").await {
            let res: String = res;
            println!("{res}"); // >>> OK
        } else {
            println!("Error setting foo");
        }

        match r.get("foo").await {
            Ok(res) => {
                let res: String = res;
                println!("{res}"); // >>> bar
            },
            Err(e) => {
                println!("Error getting foo: {e}");
                return;
            }
        };

        let hash_fields = [
            ("model", "Deimos"),
            ("brand", "Ergonom"),
            ("type", "Enduro bikes"),
            ("price", "4972"),
        ];

        if let Ok(res) = r.hset_multiple("bike:1", &hash_fields).await {
            let res: String = res;
            println!("{res}"); // >>> OK
        } else {
            println!("Error setting bike:1");
        }

        match r.hget("bike:1", "model").await {
            Ok(res) => {
                let res: String = res;
                println!("{res}"); // >>> Deimos
            },
            Err(e) => {
                println!("Error getting bike:1 model: {e}");
                return;
            }
        }

        match r.hget("bike:1", "price").await {
            Ok(res) => {
                let res: String = res;
                println!("{res}"); // >>> 4972
            },
            Err(e) => {
                println!("Error getting bike:1 price: {e}");
                return;
            }
        }

        match r.hgetall("bike:1").await {
            Ok(res) => {
                let res: Vec<(String, String)> = res;

                for (key, value) in res {
                    println!("{key}: {value}");
                }
                // >>> model: Deimos
                // >>> brand: Ergonom
                // >>> type: Enduro bikes
                // >>> price: 4972
            },
            Err(e) => {
                println!("Error getting bike:1: {e}");
                return;
            }
        }
    }
}

code:
mod landing_tests {
    use redis::Commands;

    fn run() {
        let mut r = match redis::Client::open("redis://127.0.0.1") {
            Ok(client) => {
                match client.get_connection() {
                    Ok(conn) => conn,
                    Err(e) => {
                        println!("Failed to connect to Redis: {e}");
                        return;
                    }
                }
            },
            Err(e) => {
                println!("Failed to create Redis client: {e}");
                return;
            }
        };

        if let Ok(res) = r.set("foo", "bar") {
            let res: String = res;
            println!("{res}");    // >>> OK
        } else {
            println!("Error setting foo");
        }

        match r.get("foo") {
            Ok(res) => {
                let res: String = res;
                println!("{res}");   // >>> bar
            },
            Err(e) => {
                println!("Error getting foo: {e}");
                return;
            }
        };

        let hash_fields = [
            ("model", "Deimos"),
            ("brand", "Ergonom"),
            ("type", "Enduro bikes"),
            ("price", "4972"),
        ];

        if let Ok(res) = r.hset_multiple("bike:1", &hash_fields) {
            let res: String = res;
            println!("{res}"); // >>> OK
        } else {
            println!("Error setting bike:1");
        }

        match r.hget("bike:1", "model") {
            Ok(res) => {
                let res: String = res;
                println!("{res}"); // >>> Deimos
            },
            Err(e) => {
                println!("Error getting bike:1 model: {e}");
                return;
            }
        }

        match r.hget("bike:1", "price") {
            Ok(res) => {
                let res: String = res;
                println!("{res}"); // >>> 4972
            },
            Err(e) => {
                println!("Error getting bike:1 price: {e}");
                return;
            }
        }

        match r.hgetall("bike:1") {
            Ok(res) => {
                let res: Vec<(String, String)> = res;

                for (key, value) in res {
                    println!("{key}: {value}");
                }
                // >>> model: Deimos
                // >>> brand: Ergonom
                // >>> type: Enduro bikes
                // >>> price: 4972
            },
            Err(e) => {
                println!("Error getting bike:1: {e}");
                return;
            }
        }
    }
}


title: Production usage

page_type: content

title: Overview

role: overview

text:
This guide offers recommendations to get the best reliability and
performance in your production environment.

title: Checklist

role: content

text:
Each item in the checklist below links to the section
for a recommendation. Use the checklist icons to record your
progress in implementing the recommendations.

[code example]

title: Recommendations

text:
The sections below offer recommendations for your production environment. Some
of them may not apply to your particular use case.

title: Timeouts

text:
Lettuce provides timeouts for many operations, such as command execution, SSL handshake, and Sentinel discovery. By default, Lettuce uses a global timeout value of 60 seconds for these operations, but you can override the global timeout value with individual timeout values for each operation.

Choosing suitable timeout values is crucial for your application's performance and stability and is specific to each environment.
Configuring timeouts is only necessary if you have issues with the default values.
In some cases, the defaults are based on environment-specific settings (e.g., operating system settings), while in other cases, they are built into the Lettuce driver.
For more details on setting specific timeouts, see the [Lettuce reference guide](https://redis.github.io/lettuce/).

title: Prerequisites

text:
To set TCP-level timeouts, you need to ensure you have one of [Netty Native Transports](https://netty.io/wiki/native-transports.html) installed. The most common one is `netty-transport-native-epoll`, which is used for Linux systems. You can add it to your project by including the following dependency in your `pom.xml` file:

[code example]

Once you have the native transport dependency, you can verify that by using the following code:

[code example]

If the snippet above returns `false`, you need to enable debugging logging for `io.lettuce.core` and `io.netty` to see why the native transport is not available.

For more information on using Netty Native Transport, see the [Lettuce reference guide](https://redis.github.io/lettuce/advanced-usage/#native-transports).

title: Setting timeouts

role: configuration

text:
Below is an example of setting socket-level timeouts. The `TCP_USER_TIMEOUT` setting is useful for scenarios where the server stops responding without acknowledging the last request, while the `KEEPALIVE` setting is good for detecting dead connections where there is no traffic between the client and the server.

[code example]

title: Setting timeouts in Spring Data Redis

text:
If you are using Spring Data Redis, you can set timeouts and keepalive settings using `LettuceClientConfigurationBuilderCustomizer`:

[code example]

title: Cluster topology refresh

text:
The Redis Cluster configuration is dynamic and can change at runtime.
New nodes may be added, and the primary node for a specific slot can shift.
Lettuce automatically handles [MOVED]() and [ASK]() redirects, but to enhance your application's resilience, you should enable adaptive topology refreshing:

[code example]
Learn more about topology refresh configuration settings in [the reference guide](https://redis.github.io/lettuce/ha-sharding/#redis-cluster).

title: DNS cache and Redis

text:
When you connect to a Redis server with multiple endpoints, such as [Redis Software Active-Active](https://redis.com/redis-enterprise/technology/active-active-geo-distribution/), you *must*
disable the JVM's DNS cache. If a server node or proxy fails, the IP address for any database
affected by the failure will change. When this happens, your app will keep
trying to use the stale IP address if DNS caching is enabled.

Use the following code to disable the DNS cache:

[code example]

title: Exception handling

role: errors

text:
Redis handles many errors using return values from commands, but there
are also situations where exceptions can be thrown. In production code,
you should handle exceptions as they occur.

See the Error handling sections of the
[Lettuce async](https://redis.github.io/lettuce/user-guide/async-api/#error-handling) and
[Lettuce reactive](https://redis.github.io/lettuce/user-guide/reactive-api/#error-handling)
API guides to learn more about handling exceptions.

title: Connection and execution reliability

text:
By default, Lettuce uses an *at-least-once* strategy for command execution.
It will automatically reconnect after a disconnection and resume executing
any commands that were queued when the connection was lost. If you
switch to *at-most-once* execution, Lettuce will
not reconnect after a disconnection and will discard commands
instead of queuing them. You can enable at-most-once execution by setting
`autoReconnect(false)` in the
`ClientOptions` when you create the client, as shown in the example below:

[code example]

If you need finer control over which commands you want to execute in which mode, you can
configure a *replay filter* to choose the commands that should retry after a disconnection.
The example below shows a filter that retries all commands except for
[`DECR`]()
(this command is not [idempotent](https://en.wikipedia.org/wiki/Idempotence) and
so you might need to avoid executing it more than once). Note that
replay filters are only available in in Lettuce v6.6 and above.

[code example]

See
[Command execution reliability](https://redis.github.io/lettuce/advanced-usage/#command-execution-reliability)
in the Lettuce reference guide for more information.

title: Smart client handoffs

text:
*Smart client handoffs (SCH)* is a feature of Redis Cloud and
Redis Software servers that lets them actively notify clients
about planned server maintenance shortly before it happens. This
lets a client take action to avoid disruptions in service.

See [Smart client handoffs]()
for more information about SCH and
[Connect using Smart client handoffs]()
for example code.

code:
- [ ] [Timeouts](#timeouts)
- [ ] [Cluster topology refresh](#cluster-topology-refresh)
- [ ] [DNS cache and Redis](#dns-cache-and-redis)
- [ ] [Exception handling](#exception-handling)
- [ ] [Connection and execution reliability](#connection-and-execution-reliability)
- [ ] [Smart client handoffs](#seamless-client-experience)

section_id: checklist

code:
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-transport-native-epoll</artifactId>
    <version>${netty.version}</version> <!-- e.g., 4.1.118.Final -->
    <classifier>linux-x86_64</classifier>
</dependency>

section_id: prerequisites

code: logger.info("Lettuce epool is available: {}", EpollProvider.isAvailable());

code:
RedisURI redisURI = RedisURI.Builder
        .redis("localhost")
        // set the global default from the default 60 seconds to 30 seconds
        .withTimeout(Duration.ofSeconds(30))
        .build();

try (RedisClient client = RedisClient.create(redisURI)) {
    // or set specific timeouts for things such as the TCP_USER_TIMEOUT and TCP_KEEPALIVE

    // A good general rule of thumb is to follow the rule
    // TCP_USER_TIMEOUT = TCP_KEEP_IDLE+TCP_KEEPINTVL * TCP_KEEPCNT
    // in this case, 20 = 5 + 5 * 3

    SocketOptions.TcpUserTimeoutOptions tcpUserTimeout = SocketOptions.TcpUserTimeoutOptions.builder()
            .tcpUserTimeout(Duration.ofSeconds(20))
            .enable().build();

    SocketOptions.KeepAliveOptions keepAliveOptions = SocketOptions.KeepAliveOptions.builder()
            .interval(Duration.ofSeconds(5))
            .idle(Duration.ofSeconds(5))
            .count(3).enable().build();

    SocketOptions socketOptions = SocketOptions.builder()
            .tcpUserTimeout(tcpUserTimeout)
            .keepAlive(keepAliveOptions)
            .build();

    client.setOptions(ClientOptions.builder()
            .socketOptions(socketOptions)
            .build());

    StatefulRedisConnection<String, String> connection = client.connect();
    System.out.println(connection.sync().ping());
}

section_id: setting-timeouts

code:
@Bean
public LettuceClientConfigurationBuilderCustomizer lettuceClientConfigurationBuilderCustomizer() {
    return clientConfigurationBuilder -> {
        // Configure TCP User Timeout
        // This is useful for scenarios where the server stops responding without
        // acknowledging the last request
        SocketOptions.TcpUserTimeoutOptions tcpUserTimeout = SocketOptions.TcpUserTimeoutOptions.builder()
                .tcpUserTimeout(Duration.ofSeconds(20))
                .enable()
                .build();

        // Configure TCP Keep-Alive
        // This is good for detecting dead connections where there is no traffic
        // between the client and the server
        SocketOptions.KeepAliveOptions keepAliveOptions = SocketOptions.KeepAliveOptions.builder()
                .interval(Duration.ofSeconds(5))  // TCP_KEEPINTVL: interval between probes
                .idle(Duration.ofSeconds(5))      // TCP_KEEPIDLE: time before first probe
                .count(3)                         // TCP_KEEPCNT: number of probes
                .enable()
                .build();

        // Build SocketOptions with both TCP User Timeout and Keep-Alive
        SocketOptions socketOptions = SocketOptions.builder()
                .tcpUserTimeout(tcpUserTimeout)
                .keepAlive(keepAliveOptions)
                .build();

        // Build ClientOptions with the configured SocketOptions
        ClientOptions clientOptions = ClientOptions.builder()
                .socketOptions(socketOptions)
                .build();

        // Apply the client options and command timeout to the builder
        clientConfigurationBuilder
                .clientOptions(clientOptions)
                .commandTimeout(Duration.ofSeconds(30));  // Global command timeout
    };
}

section_id: setting-timeouts-in-spring-data-redis

code:
RedisURI redisURI = RedisURI.Builder
        .redis("localhost")
        // set the global default from the default 60 seconds to 30 seconds
        .withTimeout(Duration.ofSeconds(30))
        .build();

// Create a RedisClusterClient with adaptive topology refresh
try (RedisClusterClient clusterClient = RedisClusterClient.create(redisURI)) {
    // Enable TCP keep-alive and TCP user timeout just like in the standalone example
    SocketOptions.TcpUserTimeoutOptions tcpUserTimeout = SocketOptions.TcpUserTimeoutOptions.builder()
            .tcpUserTimeout(Duration.ofSeconds(20))
            .enable()
            .build();

    SocketOptions.KeepAliveOptions keepAliveOptions = SocketOptions.KeepAliveOptions.builder()
            .interval(Duration.ofSeconds(5))
            .idle(Duration.ofSeconds(5))
            .count(3)
            .enable()
            .build();

    SocketOptions socketOptions = SocketOptions.builder()
            .tcpUserTimeout(tcpUserTimeout)
            .keepAlive(keepAliveOptions)
            .build();

    // Enable adaptive topology refresh
    // Configure adaptive topology refresh options
    ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
            .enableAllAdaptiveRefreshTriggers()
            .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(30))
            .build();

    ClusterClientOptions options = ClusterClientOptions.builder()
            .topologyRefreshOptions(topologyRefreshOptions)
            .socketOptions(socketOptions).build();

    clusterClient.setOptions(options);

    StatefulRedisClusterConnection<String, String> connection = clusterClient.connect();
    System.out.println(connection.sync().ping());
    connection.close();
}

section_id: cluster-topology-refresh

section_id: dns-cache-and-redis

code:
RedisURI uri = RedisURI.Builder
                .redis("localhost", 6379)
                .withAuthentication("default", "yourPassword")
                .build();

RedisClient client = RedisClient.create(uri);

client.setOptions(ClientOptions.builder()
    .autoReconnect(false)
        .
        .
    .build());

section_id: connection-and-execution-reliability

code:
Predicate<RedisCommand<?, ?, ?> > filter =
        cmd -> cmd.getType().toString().equalsIgnoreCase("DECR");

client.setOptions(ClientOptions.builder()
    .replayFilter(filter)
    .build());


title: Index and query documents

title: Initialize

text:
Make sure that you have [Redis Open Source]()
or another Redis server available. Also install the
[Lettuce]() client library if you
haven't already done so.

Add the following dependencies. All of them are applicable to both JSON and hash,
except for the `JsonParser`, `JsonPath`, and `JsonObject` classes.

#### Code Examples

Foundational: Import required Lettuce and JSON libraries for querying JSON documents

**Difficulty:** Beginner

**Redis CLI:**

[code example]

**Available in:** Redis CLI, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce)

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

title: Create data

text:
Create some test data to add to the database:

#### Code Examples

Foundational: Define sample JSON data structures for users with fields like name, age, and city

**Difficulty:** Beginner

**Redis CLI:**

[code example]

**Available in:** Redis CLI, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce)

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

title: Add the index

text:
Connect to your Redis database. The code below shows the most
basic connection but see
[Connect to the server]()
to learn more about the available connection options.

#### Code Examples

Foundational: Establish a connection to Redis for executing search and query operations

**Difficulty:** Beginner

**Redis CLI:**

[code example]

**Available in:** Redis CLI, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce)

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

Create an index. In this example, only JSON documents with the key prefix `user:` are indexed. For more information, see [Query syntax]().

#### Code Examples

Foundational: Create a search index on JSON documents with field mappings and aliases for efficient querying

**Difficulty:** Intermediate

**Redis CLI:**

[code example]

**Available in:** Redis CLI, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce)

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

title: Add the data

text:
Add the three sets of user data to the database as
[JSON]() objects.
If you use keys with the `user:` prefix then Redis will index the
objects automatically as you add them:

#### Code Examples

Foundational: Store JSON documents in Redis using the JSON.SET command with keys matching the index prefix

**Difficulty:** Beginner

**Redis CLI:**

[code example]

**Available in:** Redis CLI, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce)

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

title: Query the data

text:
You can now use the index to search the JSON objects. The
[query]()
below searches for objects that have the text "Paul" in any field
and have an `age` value in the range 30 to 40:

#### Code Examples

Query data: Execute a full-text search combined with numeric range filtering to find matching documents

**Difficulty:** Intermediate

**Redis CLI:**

[code example]

**Available in:** Redis CLI, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce)

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

Specify query options to return only the `city` field:

#### Code Examples

Restrict query results: Use query options to project specific fields from search results, reducing data transfer

**Difficulty:** Intermediate

**Redis CLI:**

[code example]

**Available in:** Redis CLI, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce)

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

Use an
[aggregation query]()
to count all users in each city.

#### Code Examples

Aggregation: Use aggregation queries to group and count results, performing server-side data analysis

**Difficulty:** Advanced

**Redis CLI:**

[code example]

**Available in:** Redis CLI, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce)

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

title: Differences with hash documents

text:
Indexing for hash documents is very similar to JSON indexing but you
need to specify some slightly different options.

When you create the schema for a hash index, you don't need to
add aliases for the fields, since you use the basic names to access
the fields. Also, you must use `CreateArgs.TargetType.HASH` for the `On()`
option of `CreateArgs` when you create the index. The code below shows these
changes with a new index called `hash-idx:users`, which is otherwise the same as
the `idx:users` index used for JSON documents in the previous examples.

#### Code Examples

Foundational: Create a search index on hash documents with TargetType.HASH configuration

**Difficulty:** Intermediate

**Redis CLI:**

[code example]

**Available in:** Redis CLI, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce)

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

Use [`hset()`]() to add the hash
documents instead of [`jsonSet()`]().

#### Code Examples

Foundational: Store hash documents in Redis using HSET command with keys matching the index prefix

**Difficulty:** Beginner

**Redis CLI:**

[code example]

**Available in:** Redis CLI, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce)

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

The query commands work the same here for hash as they do for JSON (but
the name of the hash index is different). The results are returned in
a `List` of `SearchReply.SearchResult<String, String>` objects, as with JSON:

#### Code Examples

Query data: Execute the same search query on hash documents as you would on JSON documents

**Difficulty:** Intermediate

**Redis CLI:**

[code example]

**Available in:** Redis CLI, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce)

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

title: More information

text:
See the [Redis Search]() docs
for a full description of all query features with examples.

code:
import io.lettuce.core.*;

import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.api.async.RediSearchAsyncCommands;
import io.lettuce.core.search.arguments.*;
import io.lettuce.core.search.arguments.AggregateArgs.*;
import io.lettuce.core.search.SearchReply;
import io.lettuce.core.search.AggregationReply;

import io.lettuce.core.json.JsonParser;
import io.lettuce.core.json.JsonObject;
import io.lettuce.core.json.JsonPath;

import io.lettuce.core.api.StatefulRedisConnection;

import java.util.*;
import java.util.concurrent.CompletableFuture;

section_id: initialize

code:
import io.lettuce.core.*;

import io.lettuce.core.api.reactive.RedisReactiveCommands;
import io.lettuce.core.api.reactive.RediSearchReactiveCommands;
import io.lettuce.core.search.arguments.*;
import io.lettuce.core.search.arguments.AggregateArgs.*;
import io.lettuce.core.search.SearchReply;
import io.lettuce.core.search.AggregationReply;

import io.lettuce.core.json.JsonParser;
import io.lettuce.core.json.JsonObject;
import io.lettuce.core.json.JsonPath;

import io.lettuce.core.api.StatefulRedisConnection;

import java.util.*;
import reactor.core.publisher.Mono;

code:
JsonParser parser = asyncCommands.getJsonParser();

            JsonObject user1 = parser.createJsonObject().put("name", parser.createJsonValue("\"Paul John\""))
                    .put("email", parser.createJsonValue("\"paul.john@example.com\"")).put("age", parser.createJsonValue("42"))
                    .put("city", parser.createJsonValue("\"London\""));

            JsonObject user2 = parser.createJsonObject().put("name", parser.createJsonValue("\"Eden Zamir\""))
                    .put("email", parser.createJsonValue("\"eden.zamir@example.com\"")).put("age", parser.createJsonValue("29"))
                    .put("city", parser.createJsonValue("\"Tel Aviv\""));

            JsonObject user3 = parser.createJsonObject().put("name", parser.createJsonValue("\"Paul Zamir\""))
                    .put("email", parser.createJsonValue("\"paul.zamir@example.com\"")).put("age", parser.createJsonValue("35"))
                    .put("city", parser.createJsonValue("\"Tel Aviv\""));

section_id: create-data

code:
JsonParser parser = reactiveCommands.getJsonParser();
            JsonObject user1 = parser.createJsonObject().put("name", parser.createJsonValue("\"Paul John\""))
                    .put("email", parser.createJsonValue("\"paul.john@example.com\"")).put("age", parser.createJsonValue("42"))
                    .put("city", parser.createJsonValue("\"London\""));

            JsonObject user2 = parser.createJsonObject().put("name", parser.createJsonValue("\"Eden Zamir\""))
                    .put("email", parser.createJsonValue("\"eden.zamir@example.com\"")).put("age", parser.createJsonValue("29"))
                    .put("city", parser.createJsonValue("\"Tel Aviv\""));

            JsonObject user3 = parser.createJsonObject().put("name", parser.createJsonValue("\"Paul Zamir\""))
                    .put("email", parser.createJsonValue("\"paul.zamir@example.com\"")).put("age", parser.createJsonValue("35"))
                    .put("city", parser.createJsonValue("\"Tel Aviv\""));

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

        try (StatefulRedisConnection<String, String> connection = redisClient.connect()) {
            RedisAsyncCommands<String, String> asyncCommands = connection.async();
            RediSearchAsyncCommands<String, String> searchCommands = connection.async();
            // ...

section_id: add-the-index

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

        try (StatefulRedisConnection<String, String> connection = redisClient.connect()) {
            RedisReactiveCommands<String, String> reactiveCommands = connection.reactive();
            RediSearchReactiveCommands<String, String> searchCommands = connection.reactive();
            // ...

code:
List<FieldArgs<String>> schema = Arrays.asList(TextFieldArgs.<String> builder().name("$.name").as("name").build(),
                    NumericFieldArgs.<String> builder().name("$.age").as("age").build(),
                    TagFieldArgs.<String> builder().name("$.city").as("city").build());

            CreateArgs<String, String> createArgs = CreateArgs.<String, String> builder().on(CreateArgs.TargetType.JSON)
                    .withPrefix("user:").build();

            CompletableFuture<Void> make_index = searchCommands.ftCreate("idx:users", createArgs, schema)
                    .thenAccept(System.out::println) // >>> OK
                    .toCompletableFuture();

code:
List<FieldArgs<String>> schema = Arrays.asList(TextFieldArgs.<String> builder().name("$.name").as("name").build(),
                    NumericFieldArgs.<String> builder().name("$.age").as("age").build(),
                    TagFieldArgs.<String> builder().name("$.city").as("city").build());

            CreateArgs<String, String> createArgs = CreateArgs.<String, String> builder().on(CreateArgs.TargetType.JSON)
                    .withPrefix("user:").build();

            Mono<Void> make_index = searchCommands.ftCreate("idx:users", createArgs, schema).doOnNext(res -> {
                System.out.println(res); // >>> OK
            }).then();

code:
CompletableFuture<String> addUser1 = asyncCommands.jsonSet("user:1", JsonPath.ROOT_PATH, user1).thenApply(r -> {
                System.out.println(r); // >>> OK
                return r;
            }).toCompletableFuture();

            CompletableFuture<String> addUser2 = asyncCommands.jsonSet("user:2", JsonPath.ROOT_PATH, user2).thenApply(r -> {
                System.out.println(r); // >>> OK
                return r;
            }).toCompletableFuture();

            CompletableFuture<String> addUser3 = asyncCommands.jsonSet("user:3", JsonPath.ROOT_PATH, user3).thenApply(r -> {
                System.out.println(r); // >>> OK
                return r;
            }).toCompletableFuture();

section_id: add-the-data

code:
Mono<String> addUser1 = reactiveCommands.jsonSet("user:1", JsonPath.ROOT_PATH, user1).doOnNext(r -> {
                System.out.println(r); // >>> OK
            });

            Mono<String> addUser2 = reactiveCommands.jsonSet("user:2", JsonPath.ROOT_PATH, user2).doOnNext(r -> {
                System.out.println(r); // >>> OK
            });

            Mono<String> addUser3 = reactiveCommands.jsonSet("user:3", JsonPath.ROOT_PATH, user3).doOnNext(r -> {
                System.out.println(r); // >>> OK
            });

code:
CompletableFuture<SearchReply<String, String>> query1 = searchCommands.ftSearch("idx:users", "Paul @age:[30 40]")
                    .thenApply(res -> {
                        List<SearchReply.SearchResult<String, String>> results = res.getResults();

                        results.forEach(result -> {
                            System.out.println(result.getId());
                        });
                        // >>> user:3
                        return res;
                    }).toCompletableFuture();

section_id: query-the-data

code:
Mono<SearchReply<String, String>> query1 = searchCommands.ftSearch("idx:users", "Paul @age:[30 40]")
                    .doOnNext(res -> {
                        List<SearchReply.SearchResult<String, String>> results = res.getResults();

                        results.forEach(result -> {
                            System.out.println(result.getId());
                        });
                        // >>> user:3
                    });

code:
SearchArgs<String, String> query2Args = SearchArgs.<String, String> builder().returnField("city").build();
            CompletableFuture<SearchReply<String, String>> query2 = searchCommands.ftSearch("idx:users", "Paul", query2Args)
                    .thenApply(res -> {
                        List<SearchReply.SearchResult<String, String>> results = res.getResults();

                        results.forEach(result -> {
                            System.out.printf("ID: %s, City: %s\n", result.getId(), result.getFields().get("city"));
                        });
                        // >>> ID: user:1, City: London
                        // >>> ID: user:3, City: Tel Aviv
                        return res;
                    }).toCompletableFuture();

code:
SearchArgs<String, String> query2Args = SearchArgs.<String, String> builder().returnField("city").build();

            Mono<SearchReply<String, String>> query2 = searchCommands.ftSearch("idx:users", "Paul", query2Args)
                    .doOnNext(res -> {
                        List<SearchReply.SearchResult<String, String>> results = res.getResults();

                        results.forEach(result -> {
                            System.out.printf("ID: %s, City: %s\n", result.getId(), result.getFields().get("city"));
                        });
                        // >>> ID: user:1, City: London
                        // >>> ID: user:3, City: Tel Aviv
                    });

code:
AggregateArgs<String, String> aggArgs = AggregateArgs.<String, String> builder()
                    .groupBy(GroupBy.<String, String> of("@city").reduce(Reducer.<String, String> count().as("count"))).build();
            CompletableFuture<AggregationReply<String, String>> query3 = searchCommands.ftAggregate("idx:users", "*", aggArgs)
                    .thenApply(res -> {
                        List<SearchReply<String, String>> replies = res.getReplies();
                        replies.forEach(reply -> {
                            reply.getResults().forEach(result -> {
                                System.out.printf("City: %s, Count: %s\n", result.getFields().get("city"),
                                        result.getFields().get("count"));
                            });
                            // >>> City: London, Count: 1
                            // >>> City: Tel Aviv, Count: 2
                        });
                        return res;
                    }).toCompletableFuture();

code:
AggregateArgs<String, String> aggArgs = AggregateArgs.<String, String> builder()
                    .groupBy(GroupBy.<String, String> of("@city").reduce(Reducer.<String, String> count().as("count"))).build();

            Mono<AggregationReply<String, String>> query3 = searchCommands.ftAggregate("idx:users", "*", aggArgs)
                    .doOnNext(res -> {
                        List<SearchReply<String, String>> replies = res.getReplies();
                        replies.forEach(reply -> {
                            reply.getResults().forEach(result -> {
                                System.out.printf("City: %s, Count: %s\n", result.getFields().get("city"),
                                        result.getFields().get("count"));
                            });
                            // >>> City: London, Count: 1
                            // >>> City: Tel Aviv, Count: 2
                        });
                    });

code:
List<FieldArgs<String>> hashSchema = Arrays.asList(TextFieldArgs.<String> builder().name("name").build(),
                    NumericFieldArgs.<String> builder().name("age").build(),
                    TagFieldArgs.<String> builder().name("city").build());

            CreateArgs<String, String> hashCreateArgs = CreateArgs.<String, String> builder().on(CreateArgs.TargetType.HASH)
                    .withPrefix("huser:").build();

            CompletableFuture<Void> makeHashIndex = searchCommands.ftCreate("hash-idx:users", hashCreateArgs, hashSchema)
                    .thenAccept(System.out::println) // >>> OK
                    .toCompletableFuture();

section_id: differences-with-hash-documents

code:
List<FieldArgs<String>> hashSchema = Arrays.asList(TextFieldArgs.<String> builder().name("name").build(),
                    NumericFieldArgs.<String> builder().name("age").build(),
                    TagFieldArgs.<String> builder().name("city").build());

            CreateArgs<String, String> hashCreateArgs = CreateArgs.<String, String> builder().on(CreateArgs.TargetType.HASH)
                    .withPrefix("huser:").build();

            Mono<String> makeHashIndex = searchCommands.ftCreate("hash-idx:users", hashCreateArgs, hashSchema).doOnNext(res -> {
                System.out.println(res); // >>> OK
            });

code:
Map<String, String> huser1 = new HashMap<>();
            huser1.put("name", "Paul John");
            huser1.put("email", "paul.john@example.com");
            huser1.put("age", "42");
            huser1.put("city", "London");

            Map<String, String> huser2 = new HashMap<>();
            huser2.put("name", "Eden Zamir");
            huser2.put("email", "eden.zamir@example.com");
            huser2.put("age", "29");
            huser2.put("city", "Tel Aviv");

            Map<String, String> huser3 = new HashMap<>();
            huser3.put("name", "Paul Zamir");
            huser3.put("email", "paul.zamir@example.com");
            huser3.put("age", "35");
            huser3.put("city", "Tel Aviv");

            CompletableFuture<Long> addHashUser1 = asyncCommands.hset("huser:1", huser1).thenApply(r -> {
                System.out.println(r); // >>> OK
                return r;
            }).toCompletableFuture();

            CompletableFuture<Long> addHashUser2 = asyncCommands.hset("huser:2", huser2).thenApply(r -> {
                System.out.println(r); // >>> OK
                return r;
            }).toCompletableFuture();

            CompletableFuture<Long> addHashUser3 = asyncCommands.hset("huser:3", huser3).thenApply(r -> {
                System.out.println(r); // >>> OK
                return r;
            }).toCompletableFuture();

code:
Map<String, String> huser1 = new HashMap<>();
            huser1.put("name", "Paul John");
            huser1.put("email", "paul.john@example.com");
            huser1.put("age", "42");
            huser1.put("city", "London");

            Map<String, String> huser2 = new HashMap<>();
            huser2.put("name", "Eden Zamir");
            huser2.put("email", "eden.zamir@example.com");
            huser2.put("age", "29");
            huser2.put("city", "Tel Aviv");

            Map<String, String> huser3 = new HashMap<>();
            huser3.put("name", "Paul Zamir");
            huser3.put("email", "paul.zamir@example.com");
            huser3.put("age", "35");
            huser3.put("city", "Tel Aviv");

            Mono<Long> addHashUser1 = reactiveCommands.hset("huser:1", huser1).doOnNext(r -> {
                System.out.println(r); // >>> OK
            });

            Mono<Long> addHashUser2 = reactiveCommands.hset("huser:2", huser2).doOnNext(r -> {
                System.out.println(r); // >>> OK
            });

            Mono<Long> addHashUser3 = reactiveCommands.hset("huser:3", huser3).doOnNext(r -> {
                System.out.println(r); // >>> OK
            });

code:
CompletableFuture<SearchReply<String, String>> query1Hash = searchCommands
                    .ftSearch("hash-idx:users", "Paul @age:[30 40]").thenApply(res -> {
                        List<SearchReply.SearchResult<String, String>> results = res.getResults();

                        results.forEach(result -> {
                            System.out.println(result.getId());
                        });
                        // >>> huser:3
                        return res;
                    }).toCompletableFuture();

code:
Mono<SearchReply<String, String>> query1Hash = searchCommands.ftSearch("hash-idx:users", "Paul @age:[30 40]")
                    .doOnNext(res -> {
                        List<SearchReply.SearchResult<String, String>> results = res.getResults();

                        results.forEach(result -> {
                            System.out.println(result.getId());
                        });
                        // >>> huser:3
                    });