diff --git a/content/develop/use-cases/cache-aside/java-jedis/DemoServer.java b/content/develop/use-cases/cache-aside/java-jedis/DemoServer.java index 9ada261333..9ca8595e73 100644 --- a/content/develop/use-cases/cache-aside/java-jedis/DemoServer.java +++ b/content/develop/use-cases/cache-aside/java-jedis/DemoServer.java @@ -1,8 +1,7 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.RedisClient; import java.io.IOException; import java.io.InputStream; @@ -27,15 +26,15 @@ *

Usage:

* *
{@code
- * javac -cp jedis-5.2.0.jar RedisCache.java MockPrimaryStore.java DemoServer.java
- * java -cp .:jedis-5.2.0.jar DemoServer --port 8080 --redis-host localhost --redis-port 6379
+ * javac -cp jedis-8.0.2.jar:commons-pool2-2.13.1.jar:slf4j-api-1.7.36.jar:gson-2.14.0.jar:json-20260719.jar:redis-authx-core-0.1.1-beta2.jar RedisCache.java MockPrimaryStore.java DemoServer.java
+ * java -cp .:jedis-8.0.2.jar:commons-pool2-2.13.1.jar:slf4j-api-1.7.36.jar:gson-2.14.0.jar:json-20260719.jar:redis-authx-core-0.1.1-beta2.jar DemoServer --port 8080 --redis-host localhost --redis-port 6379
  * }
*/ public class DemoServer { private static RedisCache cache; private static MockPrimaryStore primary; - private static JedisPool jedisPool; + private static RedisClient jedis; public static void main(String[] args) { int port = 8080; @@ -67,14 +66,14 @@ public static void main(String[] args) { } try { - jedisPool = new JedisPool(new JedisPoolConfig(), redisHost, redisPort); - jedisPool.getResource().close(); + jedis = RedisClient.create(redisHost, redisPort); + jedis.ping(); } catch (Exception e) { System.err.printf("Failed to connect to Redis at %s:%d: %s%n", redisHost, redisPort, e.getMessage()); System.exit(1); } - cache = new RedisCache(jedisPool, "cache:product:", ttl, 2000, 25); + cache = new RedisCache(jedis, "cache:product:", ttl, 2000, 25); primary = new MockPrimaryStore(primaryLatencyMs); try { diff --git a/content/develop/use-cases/cache-aside/java-jedis/RedisCache.java b/content/develop/use-cases/cache-aside/java-jedis/RedisCache.java index 62fd005267..9b4d2bf39d 100644 --- a/content/develop/use-cases/cache-aside/java-jedis/RedisCache.java +++ b/content/develop/use-cases/cache-aside/java-jedis/RedisCache.java @@ -6,9 +6,8 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.function.Function; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.Transaction; +import redis.clients.jedis.AbstractTransaction; +import redis.clients.jedis.RedisClient; import redis.clients.jedis.exceptions.JedisException; /** @@ -31,7 +30,7 @@ public class RedisCache { private static final SecureRandom RANDOM = new SecureRandom(); - private final JedisPool pool; + private final RedisClient jedis; private final String prefix; private final int ttl; private final int lockTtlMs; @@ -41,18 +40,18 @@ public class RedisCache { private final AtomicLong misses = new AtomicLong(); private final AtomicLong stampedesSuppressed = new AtomicLong(); - public RedisCache(JedisPool pool) { - this(pool, "cache:product:", 30, 2000, 25); + public RedisCache(RedisClient jedis) { + this(jedis, "cache:product:", 30, 2000, 25); } - public RedisCache(JedisPool pool, String prefix, int ttl, int lockTtlMs, int waitPollMs) { - if (pool == null) { - throw new IllegalArgumentException("pool is required"); + public RedisCache(RedisClient jedis, String prefix, int ttl, int lockTtlMs, int waitPollMs) { + if (jedis == null) { + throw new IllegalArgumentException("jedis is required"); } if (ttl < 1) { throw new IllegalArgumentException("ttl must be at least 1 second"); } - this.pool = pool; + this.jedis = jedis; this.prefix = (prefix == null || prefix.isEmpty()) ? "cache:product:" : prefix; this.ttl = ttl; this.lockTtlMs = lockTtlMs; @@ -82,9 +81,7 @@ public Result get(String entityId, Function> loader) Map cached; long startedNs = System.nanoTime(); - try (Jedis jedis = pool.getResource()) { - cached = jedis.hgetAll(cacheKey); - } + cached = jedis.hgetAll(cacheKey); double redisLatencyMs = (System.nanoTime() - startedNs) / 1_000_000.0; if (cached != null && !cached.isEmpty()) { @@ -99,9 +96,7 @@ public Result get(String entityId, Function> loader) /** Delete the cached entry for entityId. */ public boolean invalidate(String entityId) { - try (Jedis jedis = pool.getResource()) { - return jedis.del(cacheKey(entityId)) == 1L; - } + return jedis.del(cacheKey(entityId)) == 1L; } /** @@ -113,13 +108,17 @@ public boolean invalidate(String entityId) { public boolean updateField(String entityId, String field, String value) { String cacheKey = cacheKey(entityId); while (true) { - try (Jedis jedis = pool.getResource()) { - jedis.watch(cacheKey); - if (!jedis.exists(cacheKey)) { - jedis.unwatch(); + // WATCH is tied to one connection, so open a transaction that + // holds it until EXEC instead of calling watch() on the client. + // Read through the same transaction: borrowing a second pooled + // connection while holding this one can deadlock a small pool. + try (AbstractTransaction tx = jedis.transaction(false)) { + tx.watch(cacheKey); + if (!tx.exists(cacheKey).get()) { + tx.unwatch(); return false; } - Transaction tx = jedis.multi(); + tx.multi(); tx.hset(cacheKey, field, value); tx.expire(cacheKey, ttl); List result = tx.exec(); @@ -133,9 +132,7 @@ public boolean updateField(String entityId, String field, String value) { /** Remaining TTL in seconds, or -2/-1 per Redis semantics. */ public long ttlRemaining(String entityId) { - try (Jedis jedis = pool.getResource()) { - return jedis.ttl(cacheKey(entityId)); - } + return jedis.ttl(cacheKey(entityId)); } /** Hit/miss/stampede counters with the derived hit rate. */ @@ -166,29 +163,28 @@ private Map loadWithSingleFlight( String lockKey = lockKey(entityId); String token = randomToken(); - try (Jedis jedis = pool.getResource()) { - Object acquired = jedis.eval( - ACQUIRE_LOCK_SCRIPT, - List.of(lockKey), - List.of(token, Integer.toString(lockTtlMs))); - if (asInt(acquired) == 1) { - try { - Map record = loader.apply(entityId); - if (record == null) { - return null; - } - Transaction tx = jedis.multi(); + Object acquired = jedis.eval( + ACQUIRE_LOCK_SCRIPT, + List.of(lockKey), + List.of(token, Integer.toString(lockTtlMs))); + if (asInt(acquired) == 1) { + try { + Map record = loader.apply(entityId); + if (record == null) { + return null; + } + try (AbstractTransaction tx = jedis.multi()) { tx.del(cacheKey); tx.hset(cacheKey, record); tx.expire(cacheKey, ttl); tx.exec(); - return record; - } finally { - try { - jedis.eval(RELEASE_LOCK_SCRIPT, List.of(lockKey), List.of(token)); - } catch (JedisException ignored) { - // Lock will expire on its own. - } + } + return record; + } finally { + try { + jedis.eval(RELEASE_LOCK_SCRIPT, List.of(lockKey), List.of(token)); + } catch (JedisException ignored) { + // Lock will expire on its own. } } } @@ -202,11 +198,9 @@ private Map loadWithSingleFlight( Thread.currentThread().interrupt(); return null; } - try (Jedis jedis = pool.getResource()) { - Map cached = jedis.hgetAll(cacheKey); - if (cached != null && !cached.isEmpty()) { - return cached; - } + Map cached = jedis.hgetAll(cacheKey); + if (cached != null && !cached.isEmpty()) { + return cached; } } return loader.apply(entityId); diff --git a/content/develop/use-cases/cache-aside/java-jedis/_index.md b/content/develop/use-cases/cache-aside/java-jedis/_index.md index a972cb0b35..0c1a8c92c4 100644 --- a/content/develop/use-cases/cache-aside/java-jedis/_index.md +++ b/content/develop/use-cases/cache-aside/java-jedis/_index.md @@ -50,12 +50,11 @@ The `RedisCache` class wraps the cache-aside operations ([source](RedisCache.java)): ```java -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.RedisClient; -JedisPool pool = new JedisPool(new JedisPoolConfig(), "localhost", 6379); +RedisClient jedis = RedisClient.create("localhost", 6379); MockPrimaryStore primary = new MockPrimaryStore(150); -RedisCache cache = new RedisCache(pool); +RedisCache cache = new RedisCache(jedis); // Read through the cache. RedisCache.Result result = cache.get("p-001", primary::read); @@ -101,9 +100,7 @@ public Result get(String entityId, Function> loader) Map cached; long startedNs = System.nanoTime(); - try (Jedis jedis = pool.getResource()) { - cached = jedis.hgetAll(cacheKey); - } + cached = jedis.hgetAll(cacheKey); double redisLatencyMs = (System.nanoTime() - startedNs) / 1_000_000.0; if (cached != null && !cached.isEmpty()) { @@ -152,24 +149,23 @@ private Map loadWithSingleFlight( String lockKey = lockKey(entityId); String token = randomToken(); - try (Jedis jedis = pool.getResource()) { - Object acquired = jedis.eval( - ACQUIRE_LOCK_SCRIPT, - List.of(lockKey), - List.of(token, Integer.toString(lockTtlMs))); - if (asInt(acquired) == 1) { - try { - Map record = loader.apply(entityId); - if (record == null) return null; - Transaction tx = jedis.multi(); + Object acquired = jedis.eval( + ACQUIRE_LOCK_SCRIPT, + List.of(lockKey), + List.of(token, Integer.toString(lockTtlMs))); + if (asInt(acquired) == 1) { + try { + Map record = loader.apply(entityId); + if (record == null) return null; + try (AbstractTransaction tx = jedis.multi()) { tx.del(cacheKey); tx.hset(cacheKey, record); tx.expire(cacheKey, ttl); tx.exec(); - return record; - } finally { - jedis.eval(RELEASE_LOCK_SCRIPT, List.of(lockKey), List.of(token)); } + return record; + } finally { + jedis.eval(RELEASE_LOCK_SCRIPT, List.of(lockKey), List.of(token)); } } @@ -177,10 +173,8 @@ private Map loadWithSingleFlight( long deadline = System.nanoTime() + (long) lockTtlMs * 1_000_000L; while (System.nanoTime() < deadline) { Thread.sleep(waitPollMs); - try (Jedis jedis = pool.getResource()) { - Map cached = jedis.hgetAll(cacheKey); - if (cached != null && !cached.isEmpty()) return cached; - } + Map cached = jedis.hgetAll(cacheKey); + if (cached != null && !cached.isEmpty()) return cached; } return loader.apply(entityId); } @@ -194,9 +188,7 @@ When a write hits the primary, the application invalidates the cache key. The ne ```java public boolean invalidate(String entityId) { - try (Jedis jedis = pool.getResource()) { - return jedis.del(cacheKey(entityId)) == 1L; - } + return jedis.del(cacheKey(entityId)) == 1L; } ``` @@ -210,13 +202,15 @@ Because each record is stored as a hash, the cache helper can also update a sing public boolean updateField(String entityId, String field, String value) { String cacheKey = cacheKey(entityId); while (true) { - try (Jedis jedis = pool.getResource()) { - jedis.watch(cacheKey); - if (!jedis.exists(cacheKey)) { - jedis.unwatch(); + // WATCH is tied to one connection, so open a transaction that + // holds it until EXEC. + try (AbstractTransaction tx = jedis.transaction(false)) { + tx.watch(cacheKey); + if (!tx.exists(cacheKey).get()) { + tx.unwatch(); return false; } - Transaction tx = jedis.multi(); + tx.multi(); tx.hset(cacheKey, field, value); tx.expire(cacheKey, ttl); List result = tx.exec(); @@ -268,10 +262,10 @@ A local demo server is included to show the cache-aside layer in action ([source](DemoServer.java)): ```bash -javac -cp jedis-5.0.0.jar:commons-pool2-2.12.1.jar:slf4j-api-1.7.36.jar:gson-2.9.1.jar:json-20230618.jar \ +javac -cp jedis-8.0.2.jar:commons-pool2-2.13.1.jar:slf4j-api-1.7.36.jar:gson-2.14.0.jar:json-20260719.jar:redis-authx-core-0.1.1-beta2.jar \ RedisCache.java MockPrimaryStore.java DemoServer.java -java -cp .:jedis-5.0.0.jar:commons-pool2-2.12.1.jar:slf4j-api-1.7.36.jar:gson-2.9.1.jar:json-20230618.jar \ +java -cp .:jedis-8.0.2.jar:commons-pool2-2.13.1.jar:slf4j-api-1.7.36.jar:gson-2.14.0.jar:json-20260719.jar:redis-authx-core-0.1.1-beta2.jar \ DemoServer --port 8080 --redis-host localhost --redis-port 6379 ``` @@ -333,9 +327,9 @@ In this demo, a missing record returns `null` and nothing is cached. In a real s The lock TTL needs to be longer than the worst-case primary read latency so a slow loader does not lose the lock midway. The unique token in `RELEASE_LOCK_SCRIPT` ensures the original caller does not delete someone else's lock if its lock has expired. -### Tune the JedisPool +### Tune the connection pool -The demo uses a default `JedisPoolConfig`. In production, set `setMaxTotal()` and `setMaxIdle()` to match your concurrency profile and server-side `maxclients`. Wrap pool acquisition in try-with-resources so connections are returned even on exceptions. +The demo uses the default jedis settings of `RedisClient.create()`. In production, build the client with `RedisClient.builder()` and a `ConnectionPoolConfig`, and set `setMaxTotal()` and `setMaxIdle()` to match your concurrency profile and server-side `maxclients`. Wrap transactions and pipelines in try-with-resources so their connections return to the jedis even on exceptions. An open transaction or pipeline holds a pooled connection, so send any reads it needs through the transaction or pipeline itself, not through the client. ### Namespace cache keys in shared Redis deployments diff --git a/content/develop/use-cases/leaderboard/java-jedis/DemoServer.java b/content/develop/use-cases/leaderboard/java-jedis/DemoServer.java index a453396490..5f17557361 100644 --- a/content/develop/use-cases/leaderboard/java-jedis/DemoServer.java +++ b/content/develop/use-cases/leaderboard/java-jedis/DemoServer.java @@ -1,8 +1,7 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.RedisClient; import java.io.IOException; import java.io.InputStream; @@ -19,8 +18,8 @@ * Redis leaderboard demo server. * * Usage: - * javac -cp jedis-5.2.0.jar RedisLeaderboard.java DemoServer.java - * java -cp .:jedis-5.2.0.jar DemoServer [--port PORT] [--redis-host HOST] [--redis-port PORT] + * javac -cp jedis-8.0.2.jar:commons-pool2-2.13.1.jar:slf4j-api-1.7.36.jar:gson-2.14.0.jar:json-20260719.jar:redis-authx-core-0.1.1-beta2.jar RedisLeaderboard.java DemoServer.java + * java -cp .:jedis-8.0.2.jar:commons-pool2-2.13.1.jar:slf4j-api-1.7.36.jar:gson-2.14.0.jar:json-20260719.jar:redis-authx-core-0.1.1-beta2.jar DemoServer [--port PORT] [--redis-host HOST] [--redis-port PORT] */ public class DemoServer { @@ -54,7 +53,7 @@ private record DemoPlayer(String userId, double score, Map metad ); private static RedisLeaderboard leaderboard; - private static JedisPool jedisPool; + private static RedisClient jedis; public static void main(String[] args) { int port = 8080; @@ -80,15 +79,15 @@ public static void main(String[] args) { } try { - jedisPool = new JedisPool(new JedisPoolConfig(), redisHost, redisPort); - jedisPool.getResource().close(); + jedis = RedisClient.create(redisHost, redisPort); + jedis.ping(); } catch (Exception e) { System.err.printf("Failed to connect to Redis: %s%n", e.getMessage()); System.err.printf("Make sure Redis is running at %s:%d%n", redisHost, redisPort); System.exit(1); } - leaderboard = new RedisLeaderboard(jedisPool, "leaderboard:demo", maxEntries); + leaderboard = new RedisLeaderboard(jedis, "leaderboard:demo", maxEntries); resetDemoData(); try { @@ -109,7 +108,7 @@ public static void main(String[] args) { Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("\nStopping server."); - jedisPool.close(); + jedis.close(); server.stop(0); })); } catch (IOException e) { diff --git a/content/develop/use-cases/leaderboard/java-jedis/RedisLeaderboard.java b/content/develop/use-cases/leaderboard/java-jedis/RedisLeaderboard.java index a3c0039971..29048bbac4 100644 --- a/content/develop/use-cases/leaderboard/java-jedis/RedisLeaderboard.java +++ b/content/develop/use-cases/leaderboard/java-jedis/RedisLeaderboard.java @@ -5,11 +5,10 @@ import java.util.Map; import java.util.Set; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; +import redis.clients.jedis.AbstractTransaction; import redis.clients.jedis.Pipeline; +import redis.clients.jedis.RedisClient; import redis.clients.jedis.Response; -import redis.clients.jedis.Transaction; import redis.clients.jedis.params.ZRangeParams; import redis.clients.jedis.resps.Tuple; @@ -30,21 +29,21 @@ public LeaderboardEntry withTrimmedUserIds(List userIds) { } } - private final JedisPool jedisPool; + private final RedisClient jedis; private final String key; private int maxEntries; - public RedisLeaderboard(JedisPool jedisPool, String key, int maxEntries) { - if (jedisPool == null) { - throw new IllegalArgumentException("jedisPool is required"); + public RedisLeaderboard(RedisClient jedis, String key, int maxEntries) { + if (jedis == null) { + throw new IllegalArgumentException("jedis is required"); } - this.jedisPool = jedisPool; + this.jedis = jedis; this.key = (key == null || key.isBlank()) ? "leaderboard:demo" : key; this.maxEntries = normalizePositiveInt(maxEntries == 0 ? 100 : maxEntries, "maxEntries"); } - public RedisLeaderboard(JedisPool jedisPool) { - this(jedisPool, "leaderboard:demo", 100); + public RedisLeaderboard(RedisClient jedis) { + this(jedis, "leaderboard:demo", 100); } public String getKey() { @@ -58,8 +57,7 @@ public int getMaxEntries() { public LeaderboardEntry upsertUser(String userId, double score, Map metadata) { Map payload = coerceMetadata(metadata); - try (Jedis jedis = jedisPool.getResource()) { - Transaction tx = jedis.multi(); + try (AbstractTransaction tx = jedis.multi()) { tx.zadd(key, score, userId); if (!payload.isEmpty()) { tx.hset(metadataKey(userId), payload); @@ -79,8 +77,7 @@ public LeaderboardEntry incrementScore(String userId, double amount, Map payload = coerceMetadata(metadata); double newScore; - try (Jedis jedis = jedisPool.getResource()) { - Transaction tx = jedis.multi(); + try (AbstractTransaction tx = jedis.multi()) { Response scoreResponse = tx.zincrby(key, amount, userId); if (!payload.isEmpty()) { tx.hset(metadataKey(userId), payload); @@ -104,13 +101,11 @@ public List setMaxEntries(int maxEntries) { public List getTop(int count) { int normalizedCount = normalizePositiveInt(count, "count"); - try (Jedis jedis = jedisPool.getResource()) { - List entries = jedis.zrangeWithScores( - key, - ZRangeParams.zrangeParams(0, normalizedCount - 1).rev() - ); - return hydrateEntries(entries, 1); - } + List entries = jedis.zrangeWithScores( + key, + ZRangeParams.zrangeParams(0, normalizedCount - 1).rev() + ); + return hydrateEntries(entries, 1); } public List getAroundRank(int rank, int count) { @@ -133,34 +128,27 @@ public List getAroundRank(int rank, int count) { } int end = start + normalizedCount - 1; - try (Jedis jedis = jedisPool.getResource()) { - List entries = jedis.zrangeWithScores( - key, - ZRangeParams.zrangeParams(start, end).rev() - ); - return hydrateEntries(entries, start + 1); - } + List entries = jedis.zrangeWithScores( + key, + ZRangeParams.zrangeParams(start, end).rev() + ); + return hydrateEntries(entries, start + 1); } public Integer getRank(String userId) { - try (Jedis jedis = jedisPool.getResource()) { - Long rank = jedis.zrevrank(key, userId); - if (rank == null) { - return null; - } - return Math.toIntExact(rank) + 1; + Long rank = jedis.zrevrank(key, userId); + if (rank == null) { + return null; } + return Math.toIntExact(rank) + 1; } public Map getUserMetadata(String userId) { - try (Jedis jedis = jedisPool.getResource()) { - return jedis.hgetAll(metadataKey(userId)); - } + return jedis.hgetAll(metadataKey(userId)); } public LeaderboardEntry getUserEntry(String userId) { - try (Jedis jedis = jedisPool.getResource()) { - Pipeline pipeline = jedis.pipelined(); + try (Pipeline pipeline = jedis.pipelined()) { Response scoreResponse = pipeline.zscore(key, userId); Response rankResponse = pipeline.zrevrank(key, userId); Response> metadataResponse = pipeline.hgetAll(metadataKey(userId)); @@ -183,24 +171,19 @@ public LeaderboardEntry getUserEntry(String userId) { } public List listAll() { - try (Jedis jedis = jedisPool.getResource()) { - List entries = jedis.zrangeWithScores( - key, - ZRangeParams.zrangeParams(0, -1).rev() - ); - return hydrateEntries(entries, 1); - } + List entries = jedis.zrangeWithScores( + key, + ZRangeParams.zrangeParams(0, -1).rev() + ); + return hydrateEntries(entries, 1); } public long getSize() { - try (Jedis jedis = jedisPool.getResource()) { - return jedis.zcard(key); - } + return jedis.zcard(key); } public boolean deleteUser(String userId) { - try (Jedis jedis = jedisPool.getResource()) { - Transaction tx = jedis.multi(); + try (AbstractTransaction tx = jedis.multi()) { Response removed = tx.zrem(key, userId); tx.del(metadataKey(userId)); tx.exec(); @@ -209,15 +192,13 @@ public boolean deleteUser(String userId) { } public void clear() { - try (Jedis jedis = jedisPool.getResource()) { - List userIds = jedis.zrange(key, 0, -1); - List keysToDelete = new ArrayList<>(); - keysToDelete.add(key); - for (String userId : userIds) { - keysToDelete.add(metadataKey(userId)); - } - jedis.del(keysToDelete.toArray(String[]::new)); + List userIds = jedis.zrange(key, 0, -1); + List keysToDelete = new ArrayList<>(); + keysToDelete.add(key); + for (String userId : userIds) { + keysToDelete.add(metadataKey(userId)); } + jedis.del(keysToDelete.toArray(String[]::new)); } private String metadataKey(String userId) { @@ -232,34 +213,30 @@ private Map coerceMetadata(Map metadata) { } private List trimToMaxEntries() { - try (Jedis jedis = jedisPool.getResource()) { - long overflow = jedis.zcard(key) - maxEntries; - if (overflow <= 0) { - return List.of(); - } - - List trimmedUserIds = jedis.zrange(key, 0, overflow - 1); - if (trimmedUserIds.isEmpty()) { - return List.of(); - } + long overflow = jedis.zcard(key) - maxEntries; + if (overflow <= 0) { + return List.of(); + } - jedis.zremrangeByRank(key, 0, overflow - 1); - deleteMetadataForUsers(trimmedUserIds); - return List.copyOf(trimmedUserIds); + List trimmedUserIds = jedis.zrange(key, 0, overflow - 1); + if (trimmedUserIds.isEmpty()) { + return List.of(); } + + jedis.zremrangeByRank(key, 0, overflow - 1); + deleteMetadataForUsers(trimmedUserIds); + return List.copyOf(trimmedUserIds); } private void deleteMetadataForUsers(List userIds) { if (userIds.isEmpty()) { return; } - try (Jedis jedis = jedisPool.getResource()) { - List keysToDelete = new ArrayList<>(); - for (String userId : userIds) { - keysToDelete.add(metadataKey(userId)); - } - jedis.del(keysToDelete.toArray(String[]::new)); + List keysToDelete = new ArrayList<>(); + for (String userId : userIds) { + keysToDelete.add(metadataKey(userId)); } + jedis.del(keysToDelete.toArray(String[]::new)); } private List hydrateEntries(List entries, int startRank) { @@ -269,24 +246,23 @@ private List hydrateEntries(List entries, int startRank List hydratedEntries = new ArrayList<>(); - try (Jedis jedis = jedisPool.getResource()) { - Pipeline pipeline = jedis.pipelined(); - List>> metadataResponses = new ArrayList<>(); + List>> metadataResponses = new ArrayList<>(); + try (Pipeline pipeline = jedis.pipelined()) { for (Tuple entry : entries) { metadataResponses.add(pipeline.hgetAll(metadataKey(entry.getElement()))); } pipeline.sync(); + } - for (int index = 0; index < entries.size(); index++) { - Tuple entry = entries.get(index); - hydratedEntries.add(new LeaderboardEntry( - startRank + index, - entry.getElement(), - entry.getScore(), - metadataResponses.get(index).get(), - Collections.emptyList() - )); - } + for (int index = 0; index < entries.size(); index++) { + Tuple entry = entries.get(index); + hydratedEntries.add(new LeaderboardEntry( + startRank + index, + entry.getElement(), + entry.getScore(), + metadataResponses.get(index).get(), + Collections.emptyList() + )); } return hydratedEntries; diff --git a/content/develop/use-cases/leaderboard/java-jedis/_index.md b/content/develop/use-cases/leaderboard/java-jedis/_index.md index 6c6d80aac2..563dcc44f3 100644 --- a/content/develop/use-cases/leaderboard/java-jedis/_index.md +++ b/content/develop/use-cases/leaderboard/java-jedis/_index.md @@ -53,14 +53,14 @@ Add the Jedis dependency to your project: redis.clients jedis - 5.2.0 + 8.0.2 ``` * If you use **Gradle**: ```groovy - implementation 'redis.clients:jedis:5.2.0' + implementation 'redis.clients:jedis:8.0.2' ``` ## The Java leaderboard @@ -71,14 +71,14 @@ The `RedisLeaderboard` class wraps common leaderboard operations ```java import java.util.Map; -import redis.clients.jedis.JedisPool; +import redis.clients.jedis.RedisClient; public class Main { public static void main(String[] args) { - JedisPool jedisPool = new JedisPool("localhost", 6379); + RedisClient jedis = RedisClient.create("localhost", 6379); RedisLeaderboard board = new RedisLeaderboard( - jedisPool, + jedis, "leaderboard:demo", 100 ); @@ -136,8 +136,7 @@ public LeaderboardEntry upsertUser( ) { Map payload = coerceMetadata(metadata); - try (Jedis jedis = jedisPool.getResource()) { - Transaction tx = jedis.multi(); + try (AbstractTransaction tx = jedis.multi()) { tx.zadd(key, score, userId); if (!payload.isEmpty()) { tx.hset(metadataKey(userId), payload); @@ -174,17 +173,15 @@ public List getAroundRank(int rank, int count) { } int end = start + normalizedCount - 1; - try (Jedis jedis = jedisPool.getResource()) { - Set entries = jedis.zrangeWithScores( - key, - ZRangeParams.zrangeParams(start, end).rev() - ); - return hydrateEntries(entries, start + 1); - } + Set entries = jedis.zrangeWithScores( + key, + ZRangeParams.zrangeParams(start, end).rev() + ); + return hydrateEntries(entries, start + 1); } ``` -Jedis operations are synchronous and thread-safe when you use `JedisPool`, which manages a pool of connections internally. +Jedis operations are synchronous and thread-safe when you use `RedisClient`, which manages a pool of connections internally. ## Metadata design @@ -217,10 +214,12 @@ A local demo server ([source](https://github.com/redis/docs/blob/main/content/de ```bash # Compile -javac -cp jedis-5.2.0.jar RedisLeaderboard.java DemoServer.java +javac -cp jedis-8.0.2.jar:commons-pool2-2.13.1.jar:slf4j-api-1.7.36.jar:gson-2.14.0.jar:json-20260719.jar:redis-authx-core-0.1.1-beta2.jar \ + RedisLeaderboard.java DemoServer.java # Run the demo server -java -cp .:jedis-5.2.0.jar DemoServer +java -cp .:jedis-8.0.2.jar:commons-pool2-2.13.1.jar:slf4j-api-1.7.36.jar:gson-2.14.0.jar:json-20260719.jar:redis-authx-core-0.1.1-beta2.jar \ + DemoServer ``` The demo provides an interactive web interface where you can: diff --git a/content/develop/use-cases/rate-limiter/java-jedis/DemoServer.java b/content/develop/use-cases/rate-limiter/java-jedis/DemoServer.java index ae22edcac0..8bdbc2cf99 100644 --- a/content/develop/use-cases/rate-limiter/java-jedis/DemoServer.java +++ b/content/develop/use-cases/rate-limiter/java-jedis/DemoServer.java @@ -13,8 +13,7 @@ import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpExchange; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.RedisClient; import java.io.IOException; import java.io.InputStream; @@ -28,7 +27,7 @@ public class DemoServer { private static TokenBucket limiter; - private static JedisPool jedisPool; + private static RedisClient jedis; private static int capacity = 10; private static int refillRate = 1; private static double refillInterval = 1.0; @@ -55,8 +54,8 @@ public static void main(String[] args) { // Initialize Redis connection try { - jedisPool = new JedisPool(new JedisPoolConfig(), redisHost, redisPort); - jedisPool.getResource().close(); // Test connection + jedis = RedisClient.create(redisHost, redisPort); + jedis.ping(); // Test connection System.out.printf("✓ Connected to Redis at %s:%d%n", redisHost, redisPort); } catch (Exception e) { System.err.printf("✗ Failed to connect to Redis: %s%n", e.getMessage()); @@ -65,7 +64,7 @@ public static void main(String[] args) { } // Initialize the rate limiter - limiter = new TokenBucket(capacity, refillRate, refillInterval, jedisPool); + limiter = new TokenBucket(capacity, refillRate, refillInterval, jedis); // Start the server try { @@ -82,7 +81,7 @@ public static void main(String[] args) { Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("\n✓ Server stopped"); - jedisPool.close(); + jedis.close(); server.stop(0); })); } catch (IOException e) { @@ -136,7 +135,7 @@ public void handle(HttpExchange exchange) throws IOException { refillInterval = Double.parseDouble(params.get("refill_interval")); } - limiter = new TokenBucket(capacity, refillRate, refillInterval, jedisPool); + limiter = new TokenBucket(capacity, refillRate, refillInterval, jedis); json = String.format( "{\"success\":true,\"config\":{\"capacity\":%d,\"refill_rate\":%d,\"refill_interval\":%.1f}}", diff --git a/content/develop/use-cases/rate-limiter/java-jedis/TokenBucket.java b/content/develop/use-cases/rate-limiter/java-jedis/TokenBucket.java index 37ec32f833..2dd3db76a5 100644 --- a/content/develop/use-cases/rate-limiter/java-jedis/TokenBucket.java +++ b/content/develop/use-cases/rate-limiter/java-jedis/TokenBucket.java @@ -4,8 +4,7 @@ import java.util.Collections; import java.util.List; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; +import redis.clients.jedis.RedisClient; import redis.clients.jedis.exceptions.JedisNoScriptException; /** @@ -20,8 +19,8 @@ * *

Example usage:

*
{@code
- * JedisPool pool = new JedisPool("localhost", 6379);
- * TokenBucket limiter = new TokenBucket(10, 1.0, 1.0, pool);
+ * RedisClient jedis = RedisClient.create("localhost", 6379);
+ * TokenBucket limiter = new TokenBucket(10, 1.0, 1.0, jedis);
  * RateLimitResult result = limiter.allow("user:123");
  * if (result.allowed()) {
  *     System.out.println("Request allowed. " + result.remaining() + " tokens remaining.");
@@ -85,7 +84,7 @@ public record RateLimitResult(boolean allowed, double remaining) {}
     private final int capacity;
     private final double refillRate;
     private final double refillInterval;
-    private final JedisPool jedisPool;
+    private final RedisClient jedis;
     private String scriptSha;
     private boolean scriptLoaded;
 
@@ -95,13 +94,13 @@ public record RateLimitResult(boolean allowed, double remaining) {}
      * @param capacity       maximum number of tokens in the bucket (default: 10)
      * @param refillRate     number of tokens added per refill interval (default: 1.0)
      * @param refillInterval time in seconds between refills (default: 1.0)
-     * @param jedisPool      Jedis connection pool for Redis operations
+     * @param jedis          pooled Jedis client for Redis operations
      */
-    public TokenBucket(int capacity, double refillRate, double refillInterval, JedisPool jedisPool) {
+    public TokenBucket(int capacity, double refillRate, double refillInterval, RedisClient jedis) {
         this.capacity = capacity;
         this.refillRate = refillRate;
         this.refillInterval = refillInterval;
-        this.jedisPool = jedisPool;
+        this.jedis = jedis;
         this.scriptSha = sha1Hex(TOKEN_BUCKET_SCRIPT);
         this.scriptLoaded = false;
     }
@@ -109,10 +108,10 @@ public TokenBucket(int capacity, double refillRate, double refillInterval, Jedis
     /**
      * Creates a token bucket with default parameters (capacity=10, refillRate=1.0, refillInterval=1.0).
      *
-     * @param jedisPool Jedis connection pool for Redis operations
+     * @param jedis pooled Jedis client for Redis operations
      */
-    public TokenBucket(JedisPool jedisPool) {
-        this(10, 1.0, 1.0, jedisPool);
+    public TokenBucket(RedisClient jedis) {
+        this(10, 1.0, 1.0, jedis);
     }
 
     /**
@@ -138,15 +137,13 @@ public RateLimitResult allow(String key) {
         );
 
         List result;
-        try (Jedis jedis = jedisPool.getResource()) {
-            try {
-                // Try EVALSHA first (faster if script is cached)
-                result = (List) jedis.evalsha(scriptSha, keys, args);
-            } catch (JedisNoScriptException e) {
-                // Script not in cache, fall back to EVAL and reload
-                result = (List) jedis.eval(TOKEN_BUCKET_SCRIPT, keys, args);
-                scriptLoaded = false;
-            }
+        try {
+            // Try EVALSHA first (faster if script is cached)
+            result = (List) jedis.evalsha(scriptSha, keys, args);
+        } catch (JedisNoScriptException e) {
+            // Script not in cache, fall back to EVAL and reload
+            result = (List) jedis.eval(TOKEN_BUCKET_SCRIPT, keys, args);
+            scriptLoaded = false;
         }
 
         boolean allowed = result.get(0) == 1L;
@@ -157,10 +154,8 @@ public RateLimitResult allow(String key) {
     /** Ensures the Lua script is loaded into Redis. */
     private void ensureScriptLoaded() {
         if (!scriptLoaded) {
-            try (Jedis jedis = jedisPool.getResource()) {
-                scriptSha = jedis.scriptLoad(TOKEN_BUCKET_SCRIPT);
-                scriptLoaded = true;
-            }
+            scriptSha = jedis.scriptLoad(TOKEN_BUCKET_SCRIPT);
+            scriptLoaded = true;
         }
     }
 
diff --git a/content/develop/use-cases/rate-limiter/java-jedis/_index.md b/content/develop/use-cases/rate-limiter/java-jedis/_index.md
index fac7fbb8ba..54de2ab9c3 100644
--- a/content/develop/use-cases/rate-limiter/java-jedis/_index.md
+++ b/content/develop/use-cases/rate-limiter/java-jedis/_index.md
@@ -26,7 +26,7 @@ Rate limiting is a critical technique for controlling the rate at which operatio
 * Ensuring fair resource allocation across multiple clients
 * Throttling background jobs or batch operations
 
-The **token bucket algorithm** is a popular rate limiting approach that allows bursts of traffic while maintaining an average rate limit over time. This guide covers the Java implementation using the [`Jedis`](/content/develop/clients/jedis/_index.md) client library, taking advantage of Java's `try-with-resources` for connection management, `JedisPool` for connection pooling, and checked exceptions for error handling.
+The **token bucket algorithm** is a popular rate limiting approach that allows bursts of traffic while maintaining an average rate limit over time. This guide covers the Java implementation using the [`Jedis`](/content/develop/clients/jedis/_index.md) client library, taking advantage of the `RedisClient` class for connection pooling and checked exceptions for error handling.
 
 ## How it works
 
@@ -126,14 +126,14 @@ Add the Jedis dependency to your project:
   
       redis.clients
       jedis
-      5.2.0
+      8.0.2
   
   ```
 
 * If you use **Gradle**:
 
   ```groovy
-  implementation 'redis.clients:jedis:5.2.0'
+  implementation 'redis.clients:jedis:8.0.2'
   ```
 
 ## Using the Java class
@@ -142,15 +142,15 @@ The `TokenBucket` class provides a thread-safe interface for rate limiting
 ([source](https://github.com/redis/docs/blob/main/content/develop/use-cases/rate-limiter/java-jedis/TokenBucket.java)):
 
 ```java
-import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.RedisClient;
 
 public class Main {
     public static void main(String[] args) {
-        // Create a Redis connection pool
-        JedisPool jedisPool = new JedisPool("localhost", 6379);
+        // Create a pooled Redis client
+        RedisClient jedis = RedisClient.create("localhost", 6379);
 
         // Create a rate limiter: 10 requests per second
-        TokenBucket limiter = new TokenBucket(10, 1, 1.0, jedisPool);
+        TokenBucket limiter = new TokenBucket(10, 1, 1.0, jedis);
 
         // Check if a request should be allowed
         TokenBucket.RateLimitResult result = limiter.allow("user:123");
@@ -166,7 +166,7 @@ public class Main {
 }
 ```
 
-Jedis operations are synchronous and thread-safe when using `JedisPool`, which manages a pool of connections internally. The `allow()` method returns a `RateLimitResult` record containing both the decision and the remaining token count.
+Jedis operations are synchronous and thread-safe when using `RedisClient`, which manages a pool of connections internally. The `allow()` method returns a `RateLimitResult` record containing both the decision and the remaining token count.
 
 ### Configuration parameters
 
@@ -205,7 +205,7 @@ The `TokenBucket` class is thread-safe. You can share a single instance across y
 
 ```java
 // Create a shared limiter instance
-TokenBucket limiter = new TokenBucket(10, 1, 1.0, jedisPool);
+TokenBucket limiter = new TokenBucket(10, 1, 1.0, jedis);
 
 // Safe to call from multiple threads
 ExecutorService executor = Executors.newFixedThreadPool(10);
@@ -240,10 +240,12 @@ A demonstration HTTP server is included to show the rate limiter in action
 
 ```bash
 # Compile
-javac -cp jedis-5.2.0.jar TokenBucket.java DemoServer.java
+javac -cp jedis-8.0.2.jar:commons-pool2-2.13.1.jar:slf4j-api-1.7.36.jar:gson-2.14.0.jar:json-20260719.jar:redis-authx-core-0.1.1-beta2.jar \
+    TokenBucket.java DemoServer.java
 
 # Run the demo server
-java -cp .:jedis-5.2.0.jar DemoServer
+java -cp .:jedis-8.0.2.jar:commons-pool2-2.13.1.jar:slf4j-api-1.7.36.jar:gson-2.14.0.jar:json-20260719.jar:redis-authx-core-0.1.1-beta2.jar \
+    DemoServer
 ```
 
 The demo provides an interactive web interface where you can:
@@ -262,7 +264,7 @@ It's common to include rate limit information in HTTP response headers:
 ```java
 int capacity = 10;
 double refillInterval = 1.0;
-TokenBucket limiter = new TokenBucket(capacity, 1, refillInterval, jedisPool);
+TokenBucket limiter = new TokenBucket(capacity, 1, refillInterval, jedis);
 
 TokenBucket.RateLimitResult result = limiter.allow("user:" + userId);
 
@@ -290,8 +292,8 @@ public class RateLimitFilter implements Filter {
 
     @Override
     public void init(FilterConfig config) {
-        JedisPool jedisPool = new JedisPool("localhost", 6379);
-        limiter = new TokenBucket(10, 1, 1.0, jedisPool);
+        RedisClient jedis = RedisClient.create("localhost", 6379);
+        limiter = new TokenBucket(10, 1, 1.0, jedis);
     }
 
     @Override
@@ -361,8 +363,7 @@ Simplest algorithm — one key per window, one `EVAL` round trip.
 ```java
 import java.util.List;
 
-import redis.clients.jedis.Jedis;
-import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.RedisClient;
 
 public class FixedWindowLimiter {
 
@@ -387,16 +388,14 @@ public class FixedWindowLimiter {
     public record Decision(boolean allowed, long retryAfterMs) {}
 
     @SuppressWarnings("unchecked")
-    public static Decision allow(JedisPool pool, String key, int limit, int windowSeconds) {
-        try (Jedis jedis = pool.getResource()) {
-            List result = (List) jedis.eval(
-                    SCRIPT,
-                    List.of(key),
-                    List.of(String.valueOf(limit), String.valueOf(windowSeconds)));
-
-            boolean allowed = result.get(0) == 1L;
-            return new Decision(allowed, allowed ? 0L : result.get(1));
-        }
+    public static Decision allow(RedisClient jedis, String key, int limit, int windowSeconds) {
+        List result = (List) jedis.eval(
+                SCRIPT,
+                List.of(key),
+                List.of(String.valueOf(limit), String.valueOf(windowSeconds)));
+
+        boolean allowed = result.get(0) == 1L;
+        return new Decision(allowed, allowed ? 0L : result.get(1));
     }
 }
 ```
@@ -413,8 +412,7 @@ Provides a true rolling window with no boundary bursts.
 import java.util.List;
 import java.util.UUID;
 
-import redis.clients.jedis.Jedis;
-import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.RedisClient;
 
 public class SlidingWindowLogLimiter {
 
@@ -450,17 +448,15 @@ public class SlidingWindowLogLimiter {
     public record Decision(boolean allowed, long retryAfterMs) {}
 
     @SuppressWarnings("unchecked")
-    public static Decision allow(JedisPool pool, String key, int limit, int windowSeconds) {
-        try (Jedis jedis = pool.getResource()) {
-            List result = (List) jedis.eval(
-                    SCRIPT,
-                    List.of(key),
-                    List.of(String.valueOf(limit),
-                            String.valueOf(windowSeconds),
-                            UUID.randomUUID().toString()));
-
-            return new Decision(result.get(0) == 1L, result.get(1));
-        }
+    public static Decision allow(RedisClient jedis, String key, int limit, int windowSeconds) {
+        List result = (List) jedis.eval(
+                SCRIPT,
+                List.of(key),
+                List.of(String.valueOf(limit),
+                        String.valueOf(windowSeconds),
+                        UUID.randomUUID().toString()));
+
+        return new Decision(result.get(0) == 1L, result.get(1));
     }
 }
 ```
@@ -478,8 +474,7 @@ to the same slot in Redis Cluster.
 ```java
 import java.util.List;
 
-import redis.clients.jedis.Jedis;
-import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.RedisClient;
 
 public class SlidingWindowCounterLimiter {
 
@@ -515,15 +510,13 @@ public class SlidingWindowCounterLimiter {
             """;
 
     @SuppressWarnings("unchecked")
-    public static boolean allow(JedisPool pool, String key, int limit, int windowSeconds) {
-        try (Jedis jedis = pool.getResource()) {
-            List result = (List) jedis.eval(
-                    SCRIPT,
-                    List.of("{" + key + "}"),
-                    List.of(String.valueOf(limit), String.valueOf(windowSeconds)));
-
-            return result.get(0) == 1L;
-        }
+    public static boolean allow(RedisClient jedis, String key, int limit, int windowSeconds) {
+        List result = (List) jedis.eval(
+                SCRIPT,
+                List.of("{" + key + "}"),
+                List.of(String.valueOf(limit), String.valueOf(windowSeconds)));
+
+        return result.get(0) == 1L;
     }
 }
 ```
@@ -540,8 +533,7 @@ policing variant — requests are allowed or denied instantly with no delay.
 ```java
 import java.util.List;
 
-import redis.clients.jedis.Jedis;
-import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.RedisClient;
 
 public class LeakyBucketLimiter {
 
@@ -586,15 +578,13 @@ public class LeakyBucketLimiter {
     public record Decision(boolean allowed, long retryAfterMs) {}
 
     @SuppressWarnings("unchecked")
-    public static Decision allow(JedisPool pool, String key, int capacity, double leakRate) {
-        try (Jedis jedis = pool.getResource()) {
-            List result = (List) jedis.eval(
-                    SCRIPT,
-                    List.of(key),
-                    List.of(String.valueOf(capacity), String.valueOf(leakRate)));
-
-            return new Decision(result.get(0) == 1L, result.get(1));
-        }
+    public static Decision allow(RedisClient jedis, String key, int capacity, double leakRate) {
+        List result = (List) jedis.eval(
+                SCRIPT,
+                List.of(key),
+                List.of(String.valueOf(capacity), String.valueOf(leakRate)));
+
+        return new Decision(result.get(0) == 1L, result.get(1));
     }
 }
 ```
diff --git a/content/develop/use-cases/session-store/java-jedis/DemoServer.java b/content/develop/use-cases/session-store/java-jedis/DemoServer.java
index aae2f1cda7..77623eba6f 100644
--- a/content/develop/use-cases/session-store/java-jedis/DemoServer.java
+++ b/content/develop/use-cases/session-store/java-jedis/DemoServer.java
@@ -1,8 +1,7 @@
 import com.sun.net.httpserver.HttpExchange;
 import com.sun.net.httpserver.HttpHandler;
 import com.sun.net.httpserver.HttpServer;
-import redis.clients.jedis.JedisPool;
-import redis.clients.jedis.JedisPoolConfig;
+import redis.clients.jedis.RedisClient;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -20,14 +19,14 @@
  * 

Usage:

* *
{@code
- * javac -cp jedis-5.2.0.jar RedisSessionStore.java DemoServer.java
- * java -cp .:jedis-5.2.0.jar DemoServer --port 8080 --redis-host localhost --redis-port 6379
+ * javac -cp jedis-8.0.2.jar:commons-pool2-2.13.1.jar:slf4j-api-1.7.36.jar:gson-2.14.0.jar:json-20260719.jar:redis-authx-core-0.1.1-beta2.jar RedisSessionStore.java DemoServer.java
+ * java -cp .:jedis-8.0.2.jar:commons-pool2-2.13.1.jar:slf4j-api-1.7.36.jar:gson-2.14.0.jar:json-20260719.jar:redis-authx-core-0.1.1-beta2.jar DemoServer --port 8080 --redis-host localhost --redis-port 6379
  * }
*/ public class DemoServer { private static RedisSessionStore sessionStore; - private static JedisPool jedisPool; + private static RedisClient jedis; public static void main(String[] args) { int port = 8080; @@ -51,9 +50,9 @@ public static void main(String[] args) { } try { - jedisPool = new JedisPool(new JedisPoolConfig(), redisHost, redisPort); - jedisPool.getResource().close(); - sessionStore = new RedisSessionStore(jedisPool); + jedis = RedisClient.create(redisHost, redisPort); + jedis.ping(); + sessionStore = new RedisSessionStore(jedis); } catch (Exception e) { System.err.printf("Failed to connect to Redis at %s:%d: %s%n", redisHost, redisPort, e.getMessage()); System.exit(1); @@ -73,7 +72,7 @@ public static void main(String[] args) { System.out.printf("Session store demo listening on http://localhost:%d%n", port); Runtime.getRuntime().addShutdownHook(new Thread(() -> { - jedisPool.close(); + jedis.close(); server.stop(0); })); } catch (IOException e) { diff --git a/content/develop/use-cases/session-store/java-jedis/RedisSessionStore.java b/content/develop/use-cases/session-store/java-jedis/RedisSessionStore.java index 9c4ff168ed..006722d1a3 100644 --- a/content/develop/use-cases/session-store/java-jedis/RedisSessionStore.java +++ b/content/develop/use-cases/session-store/java-jedis/RedisSessionStore.java @@ -7,8 +7,7 @@ import java.util.Map; import java.util.Set; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; +import redis.clients.jedis.RedisClient; import redis.clients.jedis.Pipeline; import redis.clients.jedis.Response; @@ -30,22 +29,22 @@ public class RedisSessionStore { private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ssXXX"); - private final JedisPool jedisPool; + private final RedisClient jedis; private final String prefix; private final int ttl; - public RedisSessionStore(JedisPool jedisPool, String prefix, int ttl) { - if (jedisPool == null) { - throw new IllegalArgumentException("jedisPool is required"); + public RedisSessionStore(RedisClient jedis, String prefix, int ttl) { + if (jedis == null) { + throw new IllegalArgumentException("jedis is required"); } - this.jedisPool = jedisPool; + this.jedis = jedis; this.prefix = prefix == null || prefix.isEmpty() ? "session:" : prefix; this.ttl = normalizeTtl(ttl); } - public RedisSessionStore(JedisPool jedisPool) { - this(jedisPool, "session:", 1800); + public RedisSessionStore(RedisClient jedis) { + this(jedis, "session:", 1800); } private int normalizeTtl(Integer value) { @@ -105,8 +104,7 @@ public String createSession(Map data, Integer ttlOverride) { payload.put("last_accessed_at", now); payload.put("session_ttl", String.valueOf(sessionTtl)); - try (Jedis jedis = jedisPool.getResource()) { - Pipeline pipeline = jedis.pipelined(); + try (Pipeline pipeline = jedis.pipelined()) { pipeline.hset(key, payload); pipeline.expire(key, sessionTtl); pipeline.sync(); @@ -116,31 +114,28 @@ public String createSession(Map data, Integer ttlOverride) { } public Integer getConfiguredTtl(String sessionId) { - try (Jedis jedis = jedisPool.getResource()) { - String storedTtl = jedis.hget(sessionKey(sessionId), "session_ttl"); - if (storedTtl == null) { - return null; - } - - return normalizeTtl(Integer.parseInt(storedTtl)); + String storedTtl = jedis.hget(sessionKey(sessionId), "session_ttl"); + if (storedTtl == null) { + return null; } + + return normalizeTtl(Integer.parseInt(storedTtl)); } public Map getSession(String sessionId, boolean refreshTtl) { String key = sessionKey(sessionId); - try (Jedis jedis = jedisPool.getResource()) { - Map session = jedis.hgetAll(key); - if (!isValidSession(session)) { - return null; - } + Map session = jedis.hgetAll(key); + if (!isValidSession(session)) { + return null; + } - if (!refreshTtl) { - return session; - } + if (!refreshTtl) { + return session; + } - int sessionTtl = normalizeTtl(Integer.valueOf(session.get("session_ttl"))); - Pipeline pipeline = jedis.pipelined(); + int sessionTtl = normalizeTtl(Integer.valueOf(session.get("session_ttl"))); + try (Pipeline pipeline = jedis.pipelined()) { pipeline.hset(key, "last_accessed_at", timestamp()); pipeline.expire(key, sessionTtl); Response> refreshed = pipeline.hgetAll(key); @@ -153,45 +148,43 @@ public Map getSession(String sessionId, boolean refreshTtl) { public boolean updateSession(String sessionId, Map data) { String key = sessionKey(sessionId); - try (Jedis jedis = jedisPool.getResource()) { - Map session = jedis.hgetAll(key); - if (!isValidSession(session)) { - return false; - } + Map session = jedis.hgetAll(key); + if (!isValidSession(session)) { + return false; + } - Map payload = new HashMap<>(); - for (Map.Entry entry : data.entrySet()) { - if (!RESERVED_SESSION_FIELDS.contains(entry.getKey())) { - payload.put(entry.getKey(), String.valueOf(entry.getValue())); - } + Map payload = new HashMap<>(); + for (Map.Entry entry : data.entrySet()) { + if (!RESERVED_SESSION_FIELDS.contains(entry.getKey())) { + payload.put(entry.getKey(), String.valueOf(entry.getValue())); } + } - if (payload.isEmpty()) { - return true; - } + if (payload.isEmpty()) { + return true; + } - int sessionTtl = normalizeTtl(Integer.valueOf(session.get("session_ttl"))); - payload.put("last_accessed_at", timestamp()); + int sessionTtl = normalizeTtl(Integer.valueOf(session.get("session_ttl"))); + payload.put("last_accessed_at", timestamp()); - Pipeline pipeline = jedis.pipelined(); + try (Pipeline pipeline = jedis.pipelined()) { pipeline.hset(key, payload); pipeline.expire(key, sessionTtl); pipeline.sync(); - return true; } + return true; } public Long incrementField(String sessionId, String field, long amount) { String key = sessionKey(sessionId); - try (Jedis jedis = jedisPool.getResource()) { - Map session = jedis.hgetAll(key); - if (!isValidSession(session)) { - return null; - } + Map session = jedis.hgetAll(key); + if (!isValidSession(session)) { + return null; + } - int sessionTtl = normalizeTtl(Integer.valueOf(session.get("session_ttl"))); - Pipeline pipeline = jedis.pipelined(); + int sessionTtl = normalizeTtl(Integer.valueOf(session.get("session_ttl"))); + try (Pipeline pipeline = jedis.pipelined()) { Response value = pipeline.hincrBy(key, field, amount); pipeline.hset(key, "last_accessed_at", timestamp()); pipeline.expire(key, sessionTtl); @@ -204,34 +197,29 @@ public boolean setSessionTtl(String sessionId, int ttlOverride) { String key = sessionKey(sessionId); int sessionTtl = normalizeTtl(ttlOverride); - try (Jedis jedis = jedisPool.getResource()) { - Map session = jedis.hgetAll(key); - if (!isValidSession(session)) { - return false; - } + Map session = jedis.hgetAll(key); + if (!isValidSession(session)) { + return false; + } - Map payload = Map.of( - "session_ttl", String.valueOf(sessionTtl), - "last_accessed_at", timestamp() - ); + Map payload = Map.of( + "session_ttl", String.valueOf(sessionTtl), + "last_accessed_at", timestamp() + ); - Pipeline pipeline = jedis.pipelined(); + try (Pipeline pipeline = jedis.pipelined()) { pipeline.hset(key, payload); pipeline.expire(key, sessionTtl); pipeline.sync(); - return true; } + return true; } public boolean deleteSession(String sessionId) { - try (Jedis jedis = jedisPool.getResource()) { - return jedis.del(sessionKey(sessionId)) == 1L; - } + return jedis.del(sessionKey(sessionId)) == 1L; } public long getTtl(String sessionId) { - try (Jedis jedis = jedisPool.getResource()) { - return jedis.ttl(sessionKey(sessionId)); - } + return jedis.ttl(sessionKey(sessionId)); } } diff --git a/content/develop/use-cases/session-store/java-jedis/_index.md b/content/develop/use-cases/session-store/java-jedis/_index.md index 5db8535bb6..db97e47e73 100644 --- a/content/develop/use-cases/session-store/java-jedis/_index.md +++ b/content/develop/use-cases/session-store/java-jedis/_index.md @@ -50,12 +50,12 @@ The `RedisSessionStore` class wraps the basic session operations ```java import java.util.Map; -import redis.clients.jedis.JedisPool; +import redis.clients.jedis.RedisClient; public class Main { public static void main(String[] args) { - JedisPool jedisPool = new JedisPool("localhost", 6379); - RedisSessionStore store = new RedisSessionStore(jedisPool, "session:", 1800); + RedisClient jedis = RedisClient.create("localhost", 6379); + RedisSessionStore store = new RedisSessionStore(jedis, "session:", 1800); String sessionId = store.createSession(Map.of( "username", "andrew", @@ -67,12 +67,12 @@ public class Main { store.incrementField(sessionId, "page_views", 1); store.deleteSession(sessionId); - jedisPool.close(); + jedis.close(); } } ``` -Jedis operations are synchronous, and `JedisPool` gives you safe connection reuse across requests. +Jedis operations are synchronous, and `RedisClient` gives you safe connection reuse across requests. ### Data model @@ -121,8 +121,7 @@ public String createSession(Map data, Integer ttl) { payload.put("last_accessed_at", now); payload.put("session_ttl", String.valueOf(sessionTtl)); - try (Jedis jedis = jedisPool.getResource()) { - Pipeline pipeline = jedis.pipelined(); + try (Pipeline pipeline = jedis.pipelined()) { pipeline.hset(key, payload); pipeline.expire(key, sessionTtl); pipeline.sync(); @@ -138,18 +137,17 @@ When the application reads a session, it refreshes the configured TTL so active public Map getSession(String sessionId, boolean refreshTtl) { String key = sessionKey(sessionId); - try (Jedis jedis = jedisPool.getResource()) { - Map session = jedis.hgetAll(key); - if (!isValidSession(session)) { - return null; - } + Map session = jedis.hgetAll(key); + if (!isValidSession(session)) { + return null; + } - if (!refreshTtl) { - return session; - } + if (!refreshTtl) { + return session; + } - int sessionTtl = normalizeTtl(Integer.valueOf(session.get("session_ttl"))); - Pipeline pipeline = jedis.pipelined(); + int sessionTtl = normalizeTtl(Integer.valueOf(session.get("session_ttl"))); + try (Pipeline pipeline = jedis.pipelined()) { pipeline.hset(key, "last_accessed_at", timestamp()); pipeline.expire(key, sessionTtl); Response> refreshed = pipeline.hgetAll(key); @@ -175,14 +173,14 @@ Before running the demo, make sure that: redis.clients jedis - 5.2.0 + 8.0.2 ``` * If you use **Gradle**: ```groovy - implementation 'redis.clients:jedis:5.2.0' + implementation 'redis.clients:jedis:8.0.2' ``` If your Redis server is running elsewhere, start the demo with `--redis-host` and `--redis-port`. @@ -205,8 +203,10 @@ curl -O $BASE/DemoServer.java From that directory: ```bash -javac -cp jedis-5.2.0.jar RedisSessionStore.java DemoServer.java -java -cp .:jedis-5.2.0.jar DemoServer +javac -cp jedis-8.0.2.jar:commons-pool2-2.13.1.jar:slf4j-api-1.7.36.jar:gson-2.14.0.jar:json-20260719.jar:redis-authx-core-0.1.1-beta2.jar \ + RedisSessionStore.java DemoServer.java +java -cp .:jedis-8.0.2.jar:commons-pool2-2.13.1.jar:slf4j-api-1.7.36.jar:gson-2.14.0.jar:json-20260719.jar:redis-authx-core-0.1.1-beta2.jar \ + DemoServer ``` The demo server uses standard Java libraries for HTTP handling: