Of course! Here is a comprehensive guide to using Redis with Java, covering the most popular clients, basic operations, and advanced features.

Choosing a Java Redis Client
There are two main, officially recommended clients for Java:
- Jedis: A simple, mature, and straightforward client. It's a direct, thread-unsafe wrapper around the Redis protocol. You need to manage connections yourself, often using a connection pool (like
JedisPool). - Lettuce: A modern, advanced, and thread-safe client built on Netty. It supports synchronous, asynchronous, and reactive programming models. It manages its own I/O and is generally considered the go-to choice for new applications.
Recommendation: For most new projects, Lettuce is the recommended choice due to its modern architecture and feature set. However, Jedis is still perfectly fine, especially for simpler use cases.
Setup with Lettuce (Recommended)
Step 1: Add Dependency
Add the Lettuce dependency to your pom.xml (Maven) or build.gradle (Gradle).
Maven (pom.xml):

<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.3.2.RELEASE</version> <!-- Use the latest version -->
</dependency>
Gradle (build.gradle):
implementation 'io.lettuce:lettuce-core:6.3.2.RELEASE' // Use the latest version
Step 2: Create a Redis Connection
Lettuce uses a RedisClient to manage connections. It's best practice to create a single RedisClient instance and share it across your application.
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
public class RedisExample {
public static void main(String[] args) {
// 1. Create a Redis URI
RedisURI redisUri = RedisURI.create("redis://localhost:6379");
// 2. Create a Redis Client
RedisClient redisClient = RedisClient.create(redisUri);
// 3. Create a connection (StatefulRedisConnection is thread-safe and can be shared)
// Use try-with-resources to ensure the connection is closed
try (StatefulRedisConnection<String, String> connection = redisClient.connect()) {
// 4. Get a synchronous API for executing commands
RedisCommands<String, String> syncCommands = connection.sync();
// Now you can use syncCommands to interact with Redis
System.out.println("Connected to Redis successfully!");
// Basic operations will be demonstrated below
syncCommands.set("user:1001:name", "Alice");
String name = syncCommands.get("user:1001:name");
System.out.println("Name: " + name);
} finally {
// 5. Shutdown the client to release resources
redisClient.shutdown();
}
}
}
Common Redis Operations with Lettuce
Let's explore the most common data structures.
Basic String Operations
// Assuming 'syncCommands' is a RedisCommands<String, String> instance
// SET a key-value pair
syncCommands.set("message", "Hello, Redis!");
// GET a value
String message = syncCommands.get("message");
System.out.println("Message: " + message); // Output: Message: Hello, Redis!
// SET with an expiration (TTL - Time To Live)
syncCommands.setex("temp_session", 60, "session_data_123");
// INCR (increment)
syncCommands.set("counter", "10");
syncCommands.incr("counter"); // Now it's 11
long counter = syncCommands.get("counter");
System.out.println("Counter: " + counter); // Output: Counter: 11
Hash Operations
// HSET (set a hash field)
syncCommands.hset("user:1001", "name", "Bob");
syncCommands.hset("user:1001", "email", "bob@example.com");
// HGET (get a hash field)
String email = syncCommands.hget("user:1001", "email");
System.out.println("Email: " + email); // Output: Email: bob@example.com
// HGETALL (get all fields and values)
Map<String, String> user = syncCommands.hgetall("user:1001");
System.out.println("User: " + user); // Output: User: {name=Bob, email=bob@example.com}
// HINCRBY (increment a hash field value)
syncCommands.hset("user:1001:stats", "login_count", "5");
syncCommands.hincrby("user:1001:stats", "login_count", 1);
int logins = Integer.parseInt(syncCommands.hget("user:1001:stats", "login_count"));
System.out.println("Logins: " + logins); // Output: Logins: 6
List Operations
// LPUSH (add to the head of the list)
syncCommands.lpush("notifications", "New follower", "New like");
// LRANGE (get a range of elements)
// 0 to -1 means get all elements
List<String> notifications = syncCommands.lrange("notifications", 0, -1);
System.out.println("Notifications: " + notifications); // Output: [New like, New follower]
// LPOP (remove and get the first element)
String firstNotification = syncCommands.lpop("notifications");
System.out.println("Popped: " + firstNotification); // Output: Popped: New like
Set Operations
// SADD (add members to a set)
syncCommands.sadd("tags:java", "programming", "backend", "jvm");
// SMEMBERS (get all members of a set)
Set<String> javaTags = syncCommands.smembers("tags:java");
System.out.println("Java Tags: " + javaTags); // Output: [jvm, programming, backend] (order not guaranteed)
// SISMEMBER (check if a member exists)
boolean hasTag = syncCommands.sismember("tags:java", "web");
System.out.println("Has 'web' tag? " + hasTag); // Output: Has 'web' tag? false
Sorted Set (ZSET) Operations
// ZADD (add members with a score to a sorted set)
syncCommands.zadd("leaderboard", 150, "player1");
syncCommands.zadd("leaderboard", 200, "player2");
syncCommands.zadd("leaderboard", 100, "player3");
// ZRANGE (get members in a range by score, from low to high)
// 0 to -1 means get all members, ordered by score
Set<String> allPlayers = syncCommands.zrange("leaderboard", 0, -1);
System.out.println("Leaderboard (low to high): " + allPlayers); // Output: [player3, player1, player2]
// ZREVRANGE (get members in a range by score, from high to low)
Set<String> topPlayers = syncCommands.zrevrange("leaderboard", 0, -1);
System.out.println("Leaderboard (high to low): " + topPlayers); // Output: [player2, player1, player3]
Advanced Features with Lettuce
Pipelining for Performance
Pipelining sends multiple commands to the server without waiting for a response for each one, which can significantly reduce network latency.

// Lettuce makes this very easy
List<Object> results = syncCommands.multi() // Start a pipeline
.set("key1", "value1")
.get("key1")
.set("key2", "value2")
.get("key2")
.exec(); // Execute all commands and get results
System.out.println(results); // Output: [OK, value1, OK, value2]
Transactions
Redis transactions ensure that a group of commands are executed atomically. The MULTI/EXEC block in the pipeline example above is a transaction.
Pub/Sub (Publish/Subscribe)
This is an advanced pattern for messaging. The API is slightly different as it's event-driven.
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
import io.lettuce.core.pubsub.RedisPubSubListener;
// 1. Create a Pub/Sub connection
RedisClient redisClient = RedisClient.create("redis://localhost:6379");
try (StatefulRedisPubSubConnection<String, String> pubsubConn = redisClient.connectPubSub()) {
// 2. Define a listener
RedisPubSubListener<String, String> listener = new RedisPubSubListener<>() {
@Override
public void message(String channel, String message) {
System.out.println("Message received on channel '" + channel + "': " + message);
}
@Override
public void unsubscribed(String channel, long count) {
System.out.println("Unsubscribed from channel: " + channel);
}
@Override
public void subscribed(String channel, long count) {
System.out.println("Subscribed to channel: " + channel);
}
@Override
public void message(String pattern, String channel, String message) {
System.out.println("Message received on pattern '" + pattern + "' for channel '" + channel + "': " + message);
}
};
// 3. Add the listener and subscribe to a channel
pubsubConn.addListener(listener);
pubsubConn.sync().subscribe("news");
System.out.println("Subscribed to 'news' channel. Waiting for messages...");
// In a real application, you would run this in a separate thread
// or use an event loop. Here, we'll just wait for a bit.
Thread.sleep(10000);
// 4. Publish a message (this would be done from another part of your app)
// pubsubConn.sync().publish("news", "Java is awesome!");
} finally {
redisClient.shutdown();
}
Setup with Jedis (The Classic Alternative)
If you prefer or need to use Jedis, the setup is very similar.
Step 1: Add Dependency
Maven (pom.xml):
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.1.0</version> <!-- Use the latest version -->
</dependency>
Step 2: Use a Connection Pool
Jedis is not thread-safe, so you must use a JedisPool to manage connections.
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class JedisExample {
public static void main(String[] args) {
// Configure the pool
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(10);
poolConfig.setMaxIdle(5);
// Create a connection pool
try (JedisPool jedisPool = new JedisPool(poolConfig, "localhost", 6379)) {
// Get a connection from the pool
try (Jedis jedis = jedisPool.getResource()) {
System.out.println("Connected to Redis with Jedis!");
// Basic operations
jedis.set("jedis:message", "Hello from Jedis!");
String msg = jedis.get("jedis:message");
System.out.println("Message: " + msg);
// Hash operations
jedis.hset("jedis:user:1002", "name", "Charlie");
System.out.println("User name: " + jedis.hget("jedis:user:1002", "name"));
}
// The Jedis object is returned to the pool when the try-with-resources block exits
} finally {
// The pool is closed and all resources are released
jedisPool.close();
}
}
}
The API for jedis (e.g., jedis.set(), jedis.hgetall()) is almost identical to the synchronous API of Lettuce.
