Java Wrapper - valkey-io/valkey-glide GitHub Wiki
Please refer to the README of the Java examples for the instructions on how to run StandaloneExample and ClusterExample.
Valkey GLIDE provides support for both Cluster and Standalone and configurations. Please refer to the relevant section based on your specific setup.
Valkey GLIDE supports Cluster deployments, where the database is partitioned across multiple primary shards, with each shard being represented by a primary node and zero or more replica nodes.
To initialize a GlideClusterClient
, you need to provide a GlideClusterClientConfiguration
that includes the addresses of initial seed nodes. Valkey GLIDE automatically discovers the entire cluster topology, eliminating the necessity of explicitly listing all cluster nodes.
The NodeAddress
class represents the host and port of a cluster node. The host can be either an IP address, a hostname, or a fully qualified domain name (FQDN).
import glide.api.GlideClusterClient;
import glide.api.models.configuration.GlideClusterClientConfiguration;
import glide.api.models.configuration.NodeAddress;
NodeAddress address = NodeAddress.builder()
.host("address.example.com")
.port(6379)
.build();
GlideClusterClientConfiguration config = GlideClusterClientConfiguration.builder()
.address(address)
.build();
GlideClusterClient clusterClient = GlideClusterClient.createClient(config).get();
In the cluster, data is divided into slots, and each primary node within the cluster is responsible for specific slots. Valkey GLIDE adheres to Valkey OSS guidelines when determining the node(s) to which a command should be sent in clustering mode.
For more details on the routing of specific commands, please refer to the documentation within the code for routing configuration.
When requests are dispatched to multiple shards in a cluster (as discussed in the Request routing section), the client needs to aggregate the responses for a given command. Valkey GLIDE follows Valkey OSS guidelines for determining how to aggregate the responses from multiple shards within a cluster.
To learn more about response aggregation for specific commands, please refer to the documentation within the code.
The cluster's topology can change over time. New nodes can be added or removed, and the primary node owning a specific slot may change. Valkey GLIDE is designed to automatically rediscover the topology whenever the server indicates a change in slot ownership. This ensures that the Valkey GLIDE client stays in sync with the cluster's topology.
Valkey GLIDE also supports Standalone deployments, where the database is hosted on a single primary node, optionally with replica nodes. To initialize a GlideClient
for a standalone setup, you should create a GlideClientConfiguration
that includes the addresses of primary and all replica nodes.
import glide.api.GlideClient;
import glide.api.models.configuration.GlideClientConfiguration;
import glide.api.models.configuration.NodeAddress;
GlideClientConfiguration config = GlideClientConfiguration.builder()
.address(NodeAddress.builder()
.host("primary.example.com")
.port(6379)
.build())
.address(NodeAddress.builder()
.host("replica1.example.com")
.port(6379)
.build())
.address(NodeAddress.builder()
.host("replica2.example.com")
.port(6379)
.build())
.build();
GlideClient standaloneClient = GlideClient.createClient(config).get();
For information on the supported commands and their corresponding parameters, we recommend referring to the documentation in the code. This documentation provides in-depth insights into the usage and options available for each command.
Valkey-Glide provides an async command API. All single commands (including MULTI/EXEC, and EVAL) return a asynchronous promise to complete the command wrapped by a CompletableFuture
. The CompletableFuture
object will return a result when the action is completed, or will throw an Exception
if the action times out, is cancelled, or is interrupted. If the command is otherwise unsuccessful, the result will contain an error result and message.
Asynchronous APIs lend themselves well to concurrent calls. See the BenchmarkingApp for an example on how to execute and handle results using an ExecutorService
.
ExecutorService executor =
new ThreadPoolExecutor(
0,
Integer.MAX_VALUE,
60L,
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
// include a RejectedExecutionHandler, as some threads may be interrupted
(r, poolExecutor) -> {
if (!poolExecutor.isShutdown()) {
try {
poolExecutor.getQueue().put(r);
} catch (InterruptedException e) {
throw new RuntimeException("interrupted");
}
}
});
The action can be executed in a separate thread by the Executor
, for example:
List<CompletableFuture<String>> getCommandTasks = new ArrayList<>();
getCommandTasks.add(CompletableFuture.supplyAsync(
() -> {
try {
return client.get("myKey").get();
} catch (Exception e) {
throw new RuntimeException(e);
}
},
executor
));
CompletableFuture<String>[] completableAsyncTaskArray =
getCommandTasks.toArray(new CompletableFuture[getCommandTasks.size()]);
try {
// wait for all futures to complete
CompletableFuture.allOf(completableAsyncTaskArray).get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
Valkey strings store sequences of bytes, that may include text, serialized objects, or binary arrays. As such, to pass Valkey strings as arguments to commands, or receive Valkey strings in responses, Glide offers two APIs:
-
String
: for common caseUTF-8
converted strings keys andString
objects can be passed and receives as a JavaString
. -
GlideString
: to passbyte[]
data, aGlideString
container object can be passed as an argument to a command or received as a response from a command.
A rule about the API:
- Command signatures either take and return
String
's orGlideString
's, but not both. -
String
's are returned if the commands are passed inString
's. e.gCompletableFuture<String[]> mget(String[] keys)
-
GlideStrings
's are returned if the commands are passed inGlideStrings
's. e.gCompletableFuture<GlideString[]> mget(GlideString[] keys)
Arguments for commands that require a String
can also be substituted with GlideString
in order to pass in or return a binary value.
-
gs()
is a static constructor that can be called with abyte[]
orString
argument to convert toGlideString
. For example,
byte[] byteKey = Base64.getDecoder().decode("byteKey");
client.set(gs(byteKey), gs("GlideString value")).get();
- A
GlideString byte[]
object can be converted toUTF-8 String
by calling.getString()
on theGlideString
object. For example,
client.get(gs(byteKey)).get(); // "GlideString value" as a GlideString
client.get(gs(byteKey)).get().getString(); // "GlideString value" as a String
In Valkey Glide 2.0, the concept of Batch and ClusterBatch replaces the previous Transaction and ClusterTransaction APIs. This change provides greater flexibility by supporting both atomic batches (Transactions) and non-atomic batches (Pipelining), while ensuring easy configuration and clear, detailed examples for each scenario.
Glide 2.0 introduces a robust Batch API with two primary modes:
- Atomic Batch: Guarantees that all commands in a batch execute as a single, atomic unit. No other commands can interleave (similar to MULTI/EXEC).
- Non-Atomic Batch (Pipeline): Sends multiple commands in one request without atomic guarantees. Commands can span multiple slots/nodes in a cluster and do not block other operations from being processed between them.
Both modes leverage the same classes— Batch
for standalone mode and ClusterBatch
for cluster mode — distinguished by an isAtomic
flag. Extra configuration is provided via BatchOptions
or ClusterBatchOptions
, allowing control over timeouts, routings, and retry strategies.
Atomic Batch (Transaction)
- Definition: A set of commands executed together as a single, indivisible operation.
- Guarantees: Sequential execution without interruption. Other clients cannot interleave commands between the batched operations.
- Slot Constraint (Cluster Mode): When running against a cluster, all keys in an atomic batch must map to the same hash slot. Mixing keys from different slots will cause the transaction to fail.
-
Underlying Valkey: Equivalent to
MULTI
/EXEC
Valkey commands. - Use Case: When you need consistency and isolation.
- See: Valkey Transactions.
Non-Atomic Batch (Pipeline)
- Definition: A group of commands sent in a single request, but executed without atomicity or isolation.
- Behavior: Commands may be processed on different slots/nodes (in cluster mode), and other operations from different clients may interleave during execution.
- Underlying Valkey: Similar to pipelining, minimizing round-trip latencies by sending all commands at once.
- Use Case: Bulk reads or writes where each command is independent.
- See: Valkey Pipelines.
Batch
For standalone (non-cluster, cluster mode disabled) clients.
import glide.api.models.Batch;
// Create an atomic batch (transaction)
Batch batch = new Batch(true);
// Create a non-atomic batch (pipeline)
Batch pipeline = new Batch(false);
Note: Standalone Batches are executed on primary node.
ClusterBatch
For cluster (cluster mode enabled) clients (Mirrors Batch
but routes commands based on slot ownership, splitting into sub-pipelines if needed, Read more in Multi-Node support).
import glide.api.models.ClusterBatch;
// Create an atomic cluster batch (must use keys mapping to same slot)
ClusterBatch atomicCluster = new ClusterBatch(true);
// Create a non-atomic cluster batch (pipeline may span multiple slots)
ClusterBatch pipelineCluster = new ClusterBatch(false);
Note: When
isAtomic = true
, all keys in theClusterBatch
must map to the same slot. Attempting to include keys from different slots will result in an exception. Read more in Multi-Node support. If the client is configured to read from replicas (ReplicaPrefered, AZ_AFFINITY, AZ_AFFINITY_REPLICAS_AND_PRIMARY) read commands may be routed to the replicas, in a round robin manner, if this behavior impacts your application, consider creating a dedicated client, with the desired ReadFrom configuration.
Error handling - Raise on Error
Determines how errors are surfaced when calling exec(...)
. It is passed directly:
// Standalone Mode
CompletableFuture<Object[]> exec(Batch batch, boolean raiseOnError);
CompletableFuture<Object[]> exec(Batch batch, boolean raiseOnError, BatchOptions options);
// Cluster Mode
CompletableFuture<Object[]> exec(ClusterBatch batch, boolean raiseOnError);
CompletableFuture<Object[]> exec(ClusterBatch batch, boolean raiseOnError, ClusterBatchOptions options);
Behavior:
-
raiseOnError = true
: When set totrue
, the first encountered error within the batch (after all configured retries and redirections have been executed) is raised as aRequestException
. -
raiseOnError = false
:- When set to
false
, errors are returned as part of the response array rather than thrown. - Each failed command’s error details appear as a
RequestException
instance in the corresponding position of the returnedObject[]
. - Allows processing of both successful and failed commands together.
- When set to
Example:
// Cluster pipeline with raiseOnError = false
ClusterBatch batch = new ClusterBatch(false);
batch.set("key", "value") // OK
.lpop("key") // WRONGTYPE error (not a list)
.rename("non-existing-key", "{non-existing-key}:1"); // NO SUCH KEY error
var res = glideClusterClient.exec(batch, false).get();
System.out.println("Result is: " + Arrays.toString(res));
// Output: Result is: [OK, glide.api.models.exceptions.RequestException: WRONGTYPE: Operation against a key holding the wrong kind of value, glide.api.models.exceptions.RequestException: An error was signalled by the server: - ResponseError: no such key]
// Transaction with raiseOnError = true
Batch batch = new Batch(true)
.set("keyA", "valueA") // OK
.lpop("keyA") // WRONGTYPE error (not a list)
.get("keyB"); // Would be queued but not executed if error occurs first
try {
Object[] results = glideClient.exec(batch, true).get();
} catch (RequestException e) {
System.err.println("Batch execution aborted: " + e.getMessage());
// Example output: Batch execution aborted: WRONGTYPE: Operation against a key holding the wrong kind of value
}
BatchOptions
Configuration for standalone batches.
Option | Type | Default | Description |
---|---|---|---|
timeout |
Integer |
Client-level request timeout (e.g., 5000 ms) | Maximum time in milliseconds to wait for the batch response. If exceeded, a timeout error is returned for the batch. |
import glide.api.models.BatchOptions;
BatchOptions options = BatchOptions.builder()
.timeout(2000) // 2-second timeout
.build();
ClusterBatchOptions
Configuration for cluster batches.
Option | Type | Default | Description |
---|---|---|---|
timeout |
Integer |
Client’s requestTimeout | Maximum time in milliseconds to wait for entire cluster batch response. |
retryStrategy |
ClusterBatchRetryStrategy |
null (defaults to no retries) | Configures retry settings for server and connection errors. Not supported ifisAtomic = true — retry strategies only apply to non-atomic (pipeline) batches. |
route |
SingleNodeRoute |
null | Configures single-node routing for the batch request. |
ClusterBatchRetryStrategy
Defines retry behavior (only for non-atomic cluster batches).
Option | Type | Default | Description |
---|---|---|---|
retryServerError |
boolean |
false |
Retry commands that fail with retriable server errors (e.g.TRYAGAIN ). May cause out-of-order results. |
retryConnectionError |
boolean |
false |
Retry entire batch on connection failures. May cause duplicate executions since server might have processed the request before failure. |
import glide.api.models.BatchRetryStrategy;
ClusterBatchRetryStrategy retryStrategy = ClusterBatchRetryStrategy.builder()
.retryServerError(true)
.retryConnectionError(true)
.build();
Note: The ClusterBatchRetryStrategy configuration is only for non-atomic cluster batches, If provided for an atomic cluster batch (a cluster transaction), an error will be thrown.
Full usage
import glide.api.models.ClusterBatchOptions;
import glide.api.models.commands.batch.ClusterBatchRetryStrategy;
import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleSingleNodeRoute.RANDOM;
ClusterBatchOptions options = ClusterBatchOptions.builder()
.timeout(1000) // 1-second timeout
.retryStrategy(
ClusterBatchRetryStrategy.builder()
.retryServerError(true) // Retry on server errors
.retryConnectionError(false)// Do not retry on connection errors
.build()
)
.route(RANDOM) // Route the batch to a random node
.build();
Timeout
- Specifies the maximum time (in milliseconds) to wait for the batch (atomic or non-atomic) request to complete.
- If the timeout is reached before receiving all responses, the batch fails with a timeout error.
- Defaults to the client’s
requestTimeout
if not explicitly set.
Retry Strategies (Cluster Only, Non-Atomic Batches)
-
Retry on Server Errors
- Applies when a command fails with a retriable server error (e.g.,
TRYAGAIN
). - Glide will automatically retry the failed command on the same node or the new master, depending on the topology update.
⚠️ Caveat: Retried commands may arrive later than subsequent commands, leading to out-of-order execution if commands target the same slot.
- Applies when a command fails with a retriable server error (e.g.,
-
Retry on Connection Errors
- If a connection error occurs, the entire batch (or sub-pipeline, Read more in Multi-Node support) is retried from the start.
⚠️ Caveat: If the server received and processed some or all commands before the connection failure, retrying the batch may lead to duplicate executions.
Route (Cluster Only)
Configures single-node routing for the batch request. The client will send the batch to the specified node defined by route
.
If a redirection error occurs:
- For Atomic Batches (Transactions): The entire transaction will be redirected.
- For Non-Atomic Batches (Pipelines): only the commands that encountered redirection errors will be redirected.
Standalone (Atomic Batch)
import glide.api.GlideClient;
import glide.api.models.configuration.GlideClientConfiguration;
import glide.api.models.Batch;
import glide.api.models.BatchOptions;
import glide.api.models.exceptions.RequestException;
// Create client configuration
GlideClientConfiguration config = GlideClientConfiguration.builder()
.address(NodeAddress.builder().host("localhost").port(6379).build())
.build();
// Initialize client
GlideClient client = GlideClient.createClient(config).get();
// Configure batch options
BatchOptions options = BatchOptions.builder()
.timeout(2000) // 2-second timeout
.build();
// Create atomic batch
Batch atomicBatch = new Batch(true)
.set("account:source", "100")
.set("account:dest", "0")
.incrBy("account:dest", 50)
.decrBy("account:source", 50)
.get("account:source");
// Execute with raiseOnError = true
try {
Object[] results = client.exec(atomicBatch, true, options).get();
System.out.println("Atomic Batch Results: " + Arrays.toString(results));
// Atomic Batch Results: ["OK", "OK", 50, 50, "50"]
} catch (RequestException e) {
System.err.println("Batch failed: " + e.getMessage());
}
Standalone (Non-Atomic Batch)
import glide.api.GlideClient;
import glide.api.models.Batch;
import glide.api.models.BatchOptions;
// Create client configuration
GlideClientConfiguration config = GlideClientConfiguration.builder()
.address(NodeAddress.builder().host("localhost").port(6379).build())
.build();
// Initialize client
GlideClient client = GlideClient.createClient(config).get();
// Configure batch options
BatchOptions options = BatchOptions.builder()
.timeout(2000) // 2-second timeout
.build();
Batch pipeline = new Batch(false)
.set("temp:key1", "value1")
.set("temp:key2", "value2")
.get("temp:key1")
.get("temp:key2");
Object[] results = client.exec(pipeline, false, options).get();
System.out.println("Pipeline Results: " + Arrays.toString(results));
// Pipeline Results: ["OK", "OK", "value1", "value2"]
Cluster (Atomic Batch)
import glide.api.models.ClusterBatch;
import glide.api.models.ClusterBatchOptions;
import glide.api.models.exceptions.RequestException;
import glide.api.models.configuration.GlideClusterClientConfiguration;
import glide.api.models.NodeAddress;
import glide.api.GlideClusterClient;
// Initialize cluster client
GlideClusterClientConfiguration config = GlideClusterClientConfiguration.builder()
.address(
NodeAddress.builder()
.host("127.0.0.1")
.port(6379)
.build()
)
.build();
// Initialize client
GlideClusterClient glideClusterClient = GlideClusterClient.createClient(config).get();
// Configure atomic batch options
ClusterBatchOptions options = ClusterBatchOptions.builder()
.timeout(3000) // 3-second timeout
.build();
// Create atomic cluster batch (all keys map to same slot)
ClusterBatch atomicClusterBatch = new ClusterBatch(true)
.set("user:100:visits", "1")
.incrBy("user:100:visits", 5)
.get("user:100:visits");
// Execute with raiseOnError = true
try {
Object[] clusterResults = glideClusterClient.exec(atomicClusterBatch, true, options).get();
System.out.println("Atomic Cluster Batch: " + Arrays.toString(clusterResults));
// Expected- Atomic Cluster Batch: ["OK", 6, "6"]
} catch (RequestException e) {
System.err.println("Atomic cluster batch failed: " + e.getMessage());
}
Important: If you attempt to include keys from different slots, the batch creation will throw an exception informing you that keys must map to the same slot when
isAtomic = true
.
Cluster (Non-Atomic Batch / Pipeline)
import glide.api.models.ClusterBatch;
import glide.api.models.ClusterBatchOptions;
import glide.api.models.commands.batch.ClusterBatchRetryStrategy;
import glide.api.models.configuration.GlideClusterClientConfiguration;
import glide.api.models.NodeAddress;
import glide.api.GlideClusterClient;
// Initialize cluster client
GlideClusterClientConfiguration config = GlideClusterClientConfiguration.builder()
.address(
NodeAddress.builder()
.host("localhost")
.port(6379)
.build()
)
.build();
GlideClusterClient glideClusterClient = GlideClusterClient.createClient(config).get();
// Configure retry strategy and pipeline options
ClusterBatchRetryStrategy retryStrategy = ClusterBatchRetryStrategy.builder()
.retryServerError(false)
.retryConnectionError(true)
.build();
ClusterBatchOptions pipelineOptions = ClusterBatchOptions.builder()
.timeout(5000) // 5-second timeout
.retryStrategy(retryStrategy)
.build();
// Create pipeline spanning multiple slots
ClusterBatch pipelineCluster = new ClusterBatch(false)
.set("page:home:views", "100")
.incrBy("page:home:views", 25)
.get("page:home:views")
.lpush("recent:logins", "user1")
.lpush("recent:logins", "user2")
.lrange("recent:logins", 0, 1);
// Execute with raiseOnError = false
Object[] pipelineResults = glideClusterClient.exec(pipelineCluster, false, pipelineOptions).get();
System.out.println("Pipeline Cluster Results: " + Arrays.toString(pipelineResults));
// Pipeline Cluster Results: ["OK", 125, "125", 1, 2, ["user2", "user1"]]
While atomic batches (transactions) are restricted to a single Valkey node— all commands must map to the same hash slot in cluster mode—non-atomic batches (pipelines) can span multiple nodes. This enables operations that involve keys located in different slots or even multi-node commands.
When Glide processes a pipeline:
-
Slot Calculation and Routing: For each key-based command (e.g.,
GET
,SET
), Glide computes the hash slot and determines which node owns that slot. If a command does not reference a key (e.g.,INFO
), it follows the command’s default request policy. - Grouping into Sub-Pipelines: Commands targeting the same node are grouped together into a sub-pipeline. Each sub-pipeline contains all commands destined for a specific node.
- Dispatching Sub-Pipelines: Glide sends each sub-pipeline independently to its target node as a pipelined request.
- Aggregating Responses: Once all sub-pipelines return their results, Glide reassembles the responses into a single array, preserving the original command order. Multi-node commands are automatically split and dispatched appropriately.
Retry Strategy in Pipelines
When errors occur during pipeline execution, Glide handles them efficiently and granularly — each command in the pipeline receives its own response, whether successful or not. This means pipeline execution is not all-or-nothing: some commands may succeed while others may return errors (See the ClusterBatchRetryStrategy configuration and error handling details in the classes and API section for how to handle these errors programmatically).
Glide distinguishes between different types of errors and handles them as follows:
-
Redirection Errors (e.g.,
MOVED
orASK
): These are always handled automatically. Glide will update the topology map if needed and redirect the command to the appropriate node, regardless of the retry configuration. -
Retriable Server Errors (e.g.,
TRYAGAIN
): If theretryServerError
option is enabled in the batch's retry strategy, Glide will retry commands that fail with retriable server errors.
⚠️ Retrying may cause out-of-order execution for commands targeting the same slot. -
Connection Errors:
If the
retryConnectionError
option is enabled, Glide will retry the batch if a connection failure occurs.
⚠️ Retrying after a connection error may result in duplicate executions, since the server might have already received and processed the request before the error occurred.
Retry strategies are currently supported only for non-atomic (pipeline) cluster batches. You can configure these using the ClusterBatchRetryStrategy
options:
-
retryServerError:
Retry on server errors. -
retryConnectionError:
Retry on connection failures.
Example Scenario:
Suppose you issue the following commands:
MGET key {key}:1
SET key "value"
When keys are empty, the result is expected to be:
[null, null]
OK
However, suppose the slot of key
is migrating. In this case, both commands will return an ASK
error and be redirected.
Upon ASK
redirection, a multi-key command (like MGET
) may return a TRYAGAIN
error (triggering a retry), while the SET
command succeeds immediately.
This can result in an unintended reordering of commands if the first command is retried after the slot stabilizes:
["value", null]
OK
-
Deprecated Classes:
Transaction
andClusterTransaction
are deprecated in Glide 2.0. -
Replacement: Use
Batch
orClusterBatch
withisAtomic = true
to achieve transaction-like (atomic) behavior. -
Migration Tips:
- Replace calls to
new Transaction()
withnew Batch(true)
. - Replace calls to
new ClusterTransaction()
withnew ClusterBatch(true)
. - Replace
client.exec(transaction)
withclient.exec(batch, raiseOnError)
orclient.exec(batch, raiseOnError, options)
.
- Replace calls to
Observability is consistently one of the top feature requests by customers. Valkey GLIDE 2.0 introduces support for OpenTelemetry (OTel), enabling developers to gain deep insights into client-side performance and behavior in distributed systems. OTel is an open source, vendor-neutral framework that provides APIs, SDKs, and tools for generating, collecting, and exporting telemetry data—such as traces, metrics, and logs. It supports multiple programming languages and integrates with various observability backends like Prometheus, Jaeger, and AWS CloudWatch.
GLIDE's OpenTelemetry integration is designed to be both powerful and easy to adopt. Once an OTel collector endpoint is configured, GLIDE begins emitting default metrics and traces automatically—no additional code changes are required. This simplifies the path to observability best practices and minimizes disruption to existing workflows.
GLIDE emits several built-in metrics out of the box. These metrics can be used to build dashboards, configure alerts, and monitor performance trends:
- Timeouts: Number of requests that exceeded their timeout duration.
- Retries: Count of operations retried due to transient errors or topology changes.
- Moved Errors: Number of MOVED responses received, indicating key reallocation in the cluster.
These metrics are emitted to your configured OpenTelemetry collector and can be viewed in any supported backend (Prometheus, CloudWatch, etc.).