Valkey Java Example: GLIDE, valkey-java, Redisson and Spring Boot
To use Valkey from Java, add a client library to your build, create a client with the server's host and port, and call its methods. Valkey's client page lists three Java libraries: Valkey GLIDE and valkey-java, which the Valkey project maintains, and Redisson, which we build. This page gives a complete, runnable Valkey Java example for each one, then shows how to connect to a cluster, how to add a password and TLS, how to wire Valkey into Spring Boot, and how to test against a real server.
Every example was compiled and run on October 30, 2026 against Valkey 9.1.3, using the latest stable releases on Maven Central: GLIDE 2.5.3, valkey-java 5.5.0, Redisson 4.8.0, Spring Data Valkey 2.0.1 and Spring Boot 4.1.2. If you haven't chosen a client yet, Valkey Java clients compared covers the differences, and this page sticks to code.
Start Valkey Locally
The examples expect a Valkey server on localhost:6379. Docker is the quickest way to get one:
docker run -d --name valkey -p 6379:6379 valkey/valkey:9
docker exec valkey valkey-cli ping
The second command prints PONG once the server is up. You also need JDK 17 or later and Maven 3.9. The three client libraries run on older JDKs too, but Spring Boot 4 needs 17, and one JDK for every example keeps the setup simple.
Maven Dependencies for Each Valkey Client
Each example needs one client. Pick the dependency for the client you want to try:
| Client | Maven coordinates | What to know |
|---|---|---|
| Valkey GLIDE | io.valkey:valkey-glide:2.5.3 | Includes a native library: the plain jar (about 41 MB) bundles every supported platform, and per-platform jars (about 8 MB) take a classifier. JDK 8 or later, 11 or later recommended |
| valkey-java | io.valkey:valkey-java:5.5.0 | Pure Java. Brings commons-pool2, slf4j-api, gson and org.json |
| Redisson | org.redisson:redisson:4.8.0 | Pure Java on Netty. Java 8 or later |
Here are all three in one pom.xml:
<dependencies>
<!-- Valkey GLIDE: this jar bundles the native library for every supported platform -->
<dependency>
<groupId>io.valkey</groupId>
<artifactId>valkey-glide</artifactId>
<version>2.5.3</version>
</dependency>
<dependency>
<groupId>io.valkey</groupId>
<artifactId>valkey-java</artifactId>
<version>5.5.0</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>4.8.0</version>
</dependency>
</dependencies>
GLIDE's core is a native library, and the jar above carries a copy for every platform GLIDE supports, so one build runs on Linux, macOS and Windows. GLIDE's README still says a classifier is required, but the project has published this all-platform jar since 2.3, and Spring Data Valkey's starter depends on it.
For a smaller download, use a per-platform jar instead: add a classifier such as linux-x86_64, linux-aarch_64, osx-x86_64, osx-aarch_64 or windows-x86_64, or let the os-maven-plugin extension fill in ${os.detected.classifier}. A per-platform jar runs only on the platform it names, so a fat jar built on a Mac that way won't start on a Linux server. GLIDE also publishes linux_musl jars for Alpine, although its README still says Alpine isn't supported, so test on your image first. In Gradle, the same three dependencies are:
implementation 'io.valkey:valkey-glide:2.5.3' // or 'io.valkey:valkey-glide:2.5.3:linux-x86_64' for one platform
implementation 'io.valkey:valkey-java:5.5.0'
implementation 'org.redisson:redisson:4.8.0'
Example 1: Valkey GLIDE
This program connects, stores a string, sets a key with a 60-second expiry, increments a counter, writes a hash and reads it back:
package com.example;
import glide.api.GlideClient;
import glide.api.models.commands.SetOptions;
import glide.api.models.configuration.GlideClientConfiguration;
import glide.api.models.configuration.NodeAddress;
import java.util.Map;
public class GlideExample {
public static void main(String[] args) throws Exception {
GlideClientConfiguration config = GlideClientConfiguration.builder()
.address(NodeAddress.builder().host("localhost").port(6379).build())
.build();
// One client multiplexes every thread's commands over a single connection,
// so create it once and share it. Closing it here keeps the example short.
try (GlideClient client = GlideClient.createClient(config).get()) {
// Every command returns a CompletableFuture; get() waits for the reply.
client.set("greeting", "Hello, Valkey").get();
System.out.println(client.get("greeting").get());
// SET ... EX 60: the key expires after 60 seconds
SetOptions expireIn60s = SetOptions.builder()
.expiry(SetOptions.Expiry.Seconds(60L))
.build();
client.set("session:42", "alice", expireIn60s).get();
System.out.println("TTL: " + client.ttl("session:42").get());
System.out.println("Visits: " + client.incr("page:visits").get());
client.hset("user:1", Map.of("name", "Alice", "city", "Lisbon")).get();
System.out.println(client.hgetall("user:1").get());
// Chaining instead of blocking: the next command starts when the first completes
String name = client.hget("user:1", "name")
.thenCompose(n -> client.set("last:user", n))
.thenCompose(ok -> client.get("last:user"))
.get();
System.out.println("Last user: " + name);
}
}
}
Output:
Hello, Valkey
TTL: 60
Visits: 1
{name=Alice, city=Lisbon}
Last user: Alice
The hash fields can print in either order, because Map.of doesn't fix one. GLIDE is asynchronous: createClient and every command return a CompletableFuture. Calling get() on each one, as most of this example does, reads like synchronous code but gives up the concurrency GLIDE is built for. The last block chains the commands with thenCompose and waits once at the end.
If a reply takes longer than 250 milliseconds, the command fails with GLIDE's own glide.api.models.exceptions.TimeoutException, wrapped in an ExecutionException when you call get(). Raise the limit with requestTimeout(1000) on the configuration builder; the value is in milliseconds. Reads go to the primary unless you set readFrom.
Example 2: valkey-java
valkey-java is a fork of Jedis with the package renamed to io.valkey. Its API is synchronous, so the same program needs no futures:
package com.example;
import io.valkey.JedisPooled;
import io.valkey.params.SetParams;
import java.util.Map;
public class ValkeyJavaExample {
public static void main(String[] args) {
// JedisPooled borrows a pooled connection for each command and returns it,
// so one instance is safe to share between threads.
try (JedisPooled valkey = new JedisPooled("localhost", 6379)) {
valkey.set("greeting", "Hello, Valkey");
System.out.println(valkey.get("greeting"));
// SET ... EX 60: the key expires after 60 seconds
valkey.set("session:42", "alice", SetParams.setParams().ex(60));
System.out.println("TTL: " + valkey.ttl("session:42"));
System.out.println("Visits: " + valkey.incr("page:visits"));
valkey.hset("user:1", Map.of("name", "Alice", "city", "Lisbon"));
System.out.println(valkey.hgetAll("user:1"));
}
}
}
Output, if you run it after the GLIDE example:
Hello, Valkey
TTL: 60
Visits: 2
{name=Alice, city=Lisbon}
The counter reads 2 because both programs work on the same keys. You will also see a three-line SLF4J warning at startup. It's harmless and goes away once a logging binding is on the classpath, but the binding has to match the slf4j-api version Maven resolves: valkey-java depends on 1.7.36 and Redisson on 2.0.17, and in the pom above valkey-java's comes first and wins. mvn dependency:tree shows which one you have; with the pom above, slf4j-simple 1.7.36 works.
JedisPooled keeps a commons-pool2 pool, eight connections by default, and every command blocks the calling thread until the reply arrives. valkey-java follows the Jedis 5 API, so Jedis 5 code and tutorials carry over once the imports change from redis.clients.jedis to io.valkey. Check the release history before you build on it, though: the latest release, 5.5.0, is from October 2025. Valkey GLIDE vs valkey-java compares the two Valkey-maintained clients in detail.
Example 3: Redisson
Redisson represents Valkey data as Java objects: a string is an RBucket, a counter is an RAtomicLong, a hash is an RMap that implements java.util.Map, and a distributed lock is an RLock. This program uses all four:
package com.example;
import java.time.Duration;
import java.util.Map;
import org.redisson.Redisson;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import org.redisson.config.Config;
public class RedissonExample {
public static void main(String[] args) {
Config config = new Config();
config.useSingleServer().setAddress("valkey://127.0.0.1:6379");
// Store plain strings so valkey-cli and other clients can read these keys.
// Without this line Redisson uses its binary Kryo codec.
config.setCodec(StringCodec.INSTANCE);
// Thread-safe; create one per application and shut it down on exit
RedissonClient redisson = Redisson.create(config);
try {
RBucket<String> greeting = redisson.getBucket("greeting");
greeting.set("Hello, Valkey");
System.out.println(greeting.get());
RBucket<String> session = redisson.getBucket("session:42");
session.set("alice", Duration.ofSeconds(60));
System.out.println("TTL: " + session.remainTimeToLive() + " ms");
RAtomicLong visits = redisson.getAtomicLong("page:visits");
System.out.println("Visits: " + visits.incrementAndGet());
// RMap implements java.util.Map; each entry is a field of the Valkey hash
RMap<String, String> user = redisson.getMap("user:1");
user.putAll(Map.of("name", "Alice", "city", "Lisbon"));
System.out.println(user.readAllMap());
// Only one thread across all JVMs can hold this lock at a time
RLock lock = redisson.getLock("report:lock");
lock.lock();
try {
System.out.println("Holding report:lock");
} finally {
lock.unlock();
}
} finally {
redisson.shutdown();
}
}
}
Output, run third:
Hello, Valkey
TTL: 59999 ms
Visits: 3
{name=Alice, city=Lisbon}
Holding report:lock
The valkey:// scheme is accepted alongside redis://, and valkeys:// turns on TLS. The codec line matters as soon as more than one client touches the same data. Redisson's default Kryo codec is compact, but it writes binary values that valkey-cli shows as bytes and that GLIDE or valkey-java can't read back as strings. StringCodec stores plain text, which is why the three examples can share keys. You can also set a codec per object, for example redisson.getMap("user:1", StringCodec.INSTANCE); serialization codecs for Valkey and Redis compares the options.
Each object also has asynchronous methods such as putAsync, and redisson.reactive() and redisson.rxJava() return the same objects with Reactive Streams and RxJava 3 APIs. The lock keeps itself alive while its holder runs and expires if that JVM dies, so a crashed instance doesn't leave the report locked. How to use locks in Java goes further, and the configuration reference lists every connection setting.
The Same Operations in Each Client
Here are common operations in each client's API. GLIDE calls return a CompletableFuture, valkey-java calls return the value, and Redisson's synchronous methods return the value, with an ...Async variant returning an RFuture:
| Operation | Valkey GLIDE | valkey-java | Redisson |
|---|---|---|---|
| Set a string | client.set("k", "v") | valkey.set("k", "v") | redisson.getBucket("k") |
| Read it | client.get("k") | valkey.get("k") | bucket.get() |
| Set with a 60 s TTL | client.set("k", "v", SetOptions | valkey.setex("k", 60, "v") | bucket.set("v", Duration |
| Add a TTL to an existing key | client.expire("k", 60) | valkey.expire("k", 60) | bucket.expire(Duration |
| Increment a counter | client.incr("n") | valkey.incr("n") | redisson.getAtomicLong("n") |
| Set a hash field | client.hset("h", Map.of("f", "v")) | valkey.hset("h", "f", "v") | redisson.getMap("h") |
| Read the whole hash | client.hgetall("h") | valkey.hgetAll("h") | map.readAllMap() |
| Delete a key | client.del(new String[] {"k"}) | valkey.del("k") | bucket.delete() |
Redisson goes beyond this table with objects that have no single-command equivalent, such as locks, semaphores, rate limiters and queues, which GLIDE and valkey-java leave to your own code. For client-side caching, GLIDE has had its own cache since 2.4, and valkey-java has none.
Connecting to a Valkey Cluster
For a cluster, each client takes one or more seed nodes, discovers the rest and routes each command by its key's hash slot. With a local three-node cluster on ports 7000 to 7002 (valkey-cli --cluster create builds one), the GLIDE version is:
GlideClusterClientConfiguration config = GlideClusterClientConfiguration.builder()
.address(NodeAddress.builder().host("127.0.0.1").port(7000).build())
.build();
try (GlideClusterClient client = GlideClusterClient.createClient(config).get()) {
client.set("greeting", "Hello, Valkey Cluster").get();
System.out.println(client.get("greeting").get());
}
valkey-java uses JedisCluster, which keeps a connection pool for each node:
try (JedisCluster cluster = new JedisCluster(new HostAndPort("127.0.0.1", 7000))) {
cluster.set("greeting", "Hello, Valkey Cluster");
System.out.println(cluster.get("greeting"));
}
Redisson changes one line of configuration, and the rest of the code stays the same:
Config config = new Config();
config.useClusterServers()
.addNodeAddress("valkey://127.0.0.1:7000");
config.setCodec(StringCodec.INSTANCE);
RedissonClient redisson = Redisson.create(config);
RBucket<String> greeting = redisson.getBucket("greeting");
greeting.set("Hello, Valkey Cluster");
System.out.println(greeting.get());
redisson.shutdown();
The clients differ on where reads go. GLIDE reads from primaries unless you set readFrom, for example to ReadFrom.PREFER_REPLICA. valkey-java always reads from the primary that owns the slot. Redisson's default ReadMode.SLAVE reads from replicas when there are any; set setReadMode(ReadMode.MASTER) if every read must see the latest write. GLIDE and Redisson can also prefer replicas in the client's own availability zone, which AZ affinity in Java covers. For cluster settings in more depth, see how to connect to a cluster in Java.
Password and TLS
Managed Valkey services typically use TLS and a password, sometimes with an ACL username as well. The snippets below connect to such a server; for the built-in default user, leave the username out and pass only the password. In GLIDE, TLS and credentials are two builder calls:
String password = System.getenv("VALKEY_PASSWORD");
GlideClientConfiguration config = GlideClientConfiguration.builder()
.address(NodeAddress.builder().host("valkey.example.com").port(6380).build())
.useTLS(true)
.credentials(ServerCredentials.builder()
.username("app")
.password(password)
.build())
// Only needed when the server certificate comes from a private CA (PEM file)
.advancedConfiguration(AdvancedGlideClientConfiguration.builder()
.tlsAdvancedConfiguration(TlsAdvancedConfiguration.builder()
.rootCertificates(Files.readAllBytes(Path.of("ca.pem")))
.build())
.build())
.build();
valkey-java takes the same settings in a client configuration:
JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()
.user("app")
.password(password)
.ssl(true)
.build();
JedisPooled valkey = new JedisPooled(new HostAndPort("valkey.example.com", 6380), clientConfig);
In Redisson, the valkeys:// scheme turns on TLS, and the credentials go on the Config:
Config config = new Config();
config.useSingleServer()
.setAddress("valkeys://valkey.example.com:6380");
config.setUsername("app");
config.setPassword(password);
If the server's certificate comes from a private CA rather than a public one, each client needs to be told about it in its own way:
GLIDE checks certificates against the operating system's certificate store, not the Java trust store. Pass the CA certificate in PEM form with
rootCertificates, as in the commented lines above, or load a Java trust store withTlsAdvancedConfiguration.fromKeyStore(path, password, type). In our test, a connection without the CA didn't report a certificate problem; it failed with a connection timeout.valkey-java uses the JVM's default trust store, so import the CA into a trust store and point
javax.net.ssl.trustStoreat it, or pass your ownSSLSocketFactory.Redisson uses the JVM's default trust store too, or one you set with
config.setSslTruststore(url)andsetSslTruststorePassword(...). How to connect to Valkey or Redis over TLS covers the other TLS settings, including client certificates.
An error that starts with NOAUTH or WRONGPASS means the credentials are missing or wrong; NOAUTH Authentication required walks through the causes.
Valkey Spring Boot Example
With Spring Data Redis and Redisson
If you want to keep Spring Data Redis's API, or you want Redisson's objects in the same application, add Redisson's starter instead. It sets up a Redisson connection factory for RedisTemplate and StringRedisTemplate, and for the Spring cache once you add spring-boot-starter-cache, plus a RedissonClient bean:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>4.8.0</version>
</dependency>
spring.data.redis.host=localhost
spring.data.redis.port=6379
package com.example.demo;
import java.time.Duration;
import org.redisson.api.RLock;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.StringRedisTemplate;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
CommandLineRunner demo(StringRedisTemplate template, RedissonClient redisson) {
return args -> {
// Spring Data Redis API, running on Redisson's connection factory
template.opsForValue().set("greeting", "Hello from Spring Boot", Duration.ofMinutes(5));
System.out.println(template.opsForValue().get("greeting"));
// Redisson objects from the same starter
RMap<String, Integer> stock = redisson.getMap("stock");
stock.put("sku-1", 10);
System.out.println("sku-1: " + stock.get("sku-1"));
RLock lock = redisson.getLock("nightly-report");
// Returns false at once if another instance holds the lock. While this JVM runs,
// the watchdog renews the lock; if the JVM dies, it expires within 30 s.
if (lock.tryLock()) {
try {
System.out.println("This instance runs the report");
} finally {
lock.unlock();
}
}
};
}
}
This application keeps running after it prints, as a service would; stop it with Ctrl+C. To use a valkey:// address or any other Redisson setting, put a Redisson configuration under spring.redis.redisson.config in application.yml, or point spring.redis.redisson.file at a YAML file:
spring:
redis:
redisson:
config: |
singleServerConfig:
address: "valkey://localhost:6379"
Redis with Spring Boot covers RedisTemplate, serializers and repositories, which work the same on Valkey. For caching, the Spring Boot cache example builds a complete @Cacheable application, and Redisson and Spring Boot 4 lists which starter versions match which Boot releases. The Spring integration docs cover the starter's other settings.
Testing With Testcontainers
For integration tests, Testcontainers can start the official Valkey image for each test class. A plain GenericContainer is enough, so you don't need a Valkey-specific module. Add the test dependencies:
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-junit-jupiter</artifactId>
<version>2.0.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>6.1.3</version>
<scope>test</scope>
</dependency>
The test below uses Redisson, but the container part works the same with any client. Connect to getHost() and getMappedPort(6379), because Docker maps the server to a random free port:
package com.example;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.redisson.Redisson;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import org.redisson.config.Config;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;
@Testcontainers
class ValkeyContainerTest {
// static: one container for the whole class, started before @BeforeAll runs
@Container
static GenericContainer<?> valkey =
new GenericContainer<>(DockerImageName.parse("valkey/valkey:9")).withExposedPorts(6379);
static RedissonClient redisson;
@BeforeAll
static void connect() {
Config config = new Config();
// Docker maps 6379 to a random free port on the host
config.useSingleServer()
.setAddress("valkey://" + valkey.getHost() + ":" + valkey.getMappedPort(6379));
config.setCodec(StringCodec.INSTANCE);
redisson = Redisson.create(config);
}
@AfterAll
static void disconnect() {
redisson.shutdown();
}
@Test
void storesAndReadsAValue() {
RBucket<String> greeting = redisson.getBucket("greeting");
greeting.set("Hello, Valkey");
assertEquals("Hello, Valkey", greeting.get());
}
}
Pin the image tag your production servers run, such as valkey/valkey:9.1, so a new Valkey release doesn't change your test results without notice.
Troubleshooting
These are the errors we hit while writing the examples, and what each one means:
| Symptom | Cause | Fix |
|---|---|---|
GLIDE: RuntimeException: Failed to load Glide native library | The GLIDE jar has no native library for this OS and CPU, for example a per-platform jar resolved on another machine | Use the all-platform jar, or the classifier for the platform you run on |
GLIDE: TimeoutException: Request timed out | The reply took longer than the 250 ms default | Raise requestTimeout (milliseconds) on the configuration builder |
GLIDE over TLS times out, or fails with Connection refused, while other clients connect | The server certificate comes from a private CA that GLIDE doesn't know | Pass the CA with rootCertificates or fromKeyStore |
valkey-java: JedisConnectionException: Failed to connect to any host resolved for DNS name.Redisson: Unable to connect to Redis server | Nothing is listening at that host and port, or, over TLS, the server certificate isn't trusted | Check the host, the port and Docker's -p 6379:6379 mapping, then the trust store |
NOAUTH Authentication required or WRONGPASS | The password or username is missing or wrong | Set the credentials as shown above |
Values written by Redisson look like binary in valkey-cli, or other clients can't read them | Redisson's default Kryo codec | Use StringCodec or a JSON codec for shared data |
Which Example Should You Start From?
For the reasoning behind each choice, see Valkey Java clients compared, Valkey GLIDE vs valkey-java and Valkey GLIDE vs Redisson. Coming from Redis, the Redis in Java guide uses the same Redisson code, and migrating from Redis to Valkey in Java covers the move itself.
Frequently Asked Questions
How Do I Connect to Valkey From Java?
Add a Valkey client to your build, such as io.valkey:valkey-glide, io.valkey:valkey-java or org.redisson:redisson, and create a client with the server's host and port, 6379 by default. Then call its methods, for example set and get. Each client has a cluster mode that takes one or more seed nodes, and each supports a password, an ACL username and TLS.
What Is the Maven Dependency for Valkey in Java?
It depends on the client. As of October 2026 the latest stable versions are io.valkey:valkey-glide:2.5.3, either as the all-platform jar or with a platform classifier such as linux-x86_64, io.valkey:valkey-java:5.5.0 and org.redisson:redisson:4.8.0. For Spring Boot, the Valkey project publishes io.valkey.springframework.boot:spring-boot-starter-data-valkey:2.0.1, and Redisson publishes org.redisson:redisson-spring-boot-starter:4.8.0.
Can I Use Jedis or Lettuce With Valkey?
Yes, for the commands Valkey shares with Redis. Valkey speaks the same protocol, so both connect and run those commands. Redis maintains both clients, neither states Valkey support, and neither implements Valkey-specific client features such as replica redirect or availability-zone reads. valkey-java is the Valkey project's fork of Jedis if you want the Jedis API under Valkey's name.
Do Redis Java Examples Work With Valkey?
Mostly, yes. Valkey was forked from Redis 7.2.4 and still reports redis_version 7.2.4 for compatibility, so Java code that uses Redis 7.2 commands runs unchanged against Valkey. Differences appear with commands and modules that Redis added after the fork, and with Valkey features that Redis lacks.
Does Spring Boot Support Valkey?
Spring Boot has no Valkey starter of its own. The Valkey project publishes Spring Data Valkey with a Spring Boot starter that targets Spring Boot 4.1 and uses GLIDE by default. Alternatively, Spring Data Redis connects to Valkey unchanged, with Lettuce, Jedis or Redisson as the driver.