cacheConfigurations) {
- RedisAssertions.requireNonNull(cacheConfigurations, "CacheConfigurations must not be null")
- .forEach((cacheName, cacheConfiguration) -> RedisAssertions.requireNonNull(cacheConfiguration,
- "RedisCacheConfiguration for cache [%s] must not be null", cacheName));
+ Assert.notNull(cacheConfigurations, "CacheConfigurations must not be null!");
+ cacheConfigurations.forEach((cacheName, configuration) -> Assert.notNull(configuration,
+ String.format("RedisCacheConfiguration for cache %s must not be null!", cacheName)));
this.initialCaches.putAll(cacheConfigurations);
diff --git a/src/main/java/org/springframework/data/redis/cache/RedisCacheWriter.java b/src/main/java/org/springframework/data/redis/cache/RedisCacheWriter.java
index 04dd0e3507..e5e3d16b2b 100644
--- a/src/main/java/org/springframework/data/redis/cache/RedisCacheWriter.java
+++ b/src/main/java/org/springframework/data/redis/cache/RedisCacheWriter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2024 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,14 +24,14 @@
import org.springframework.util.Assert;
/**
- * {@link RedisCacheWriter} provides low-level access to Redis commands ({@code SET, SETNX, GET, EXPIRE,...})
- * used for caching.
+ * {@link RedisCacheWriter} provides low-level access to Redis commands ({@code SET, SETNX, GET, EXPIRE,...}) used for
+ * caching.
*
* The {@link RedisCacheWriter} may be shared by multiple cache implementations and is responsible for reading/writing
* binary data from/to Redis. The implementation honors potential cache lock flags that might be set.
*
- * The default {@link RedisCacheWriter} implementation can be customized with {@link BatchStrategy}
- * to tune performance behavior.
+ * The default {@link RedisCacheWriter} implementation can be customized with {@link BatchStrategy} to tune performance
+ * behavior.
*
* @author Christoph Strobl
* @author Mark Paluch
@@ -96,9 +96,8 @@ static RedisCacheWriter lockingRedisCacheWriter(RedisConnectionFactory connectio
*
* @param connectionFactory must not be {@literal null}.
* @param sleepTime sleep time between lock access attempts, must not be {@literal null}.
- * @param lockTtlFunction TTL function to compute the Lock TTL. The function is called with contextual keys
- * and values (such as the cache name on cleanup or the actual key/value on put requests);
- * must not be {@literal null}.
+ * @param lockTtlFunction TTL function to compute the Lock TTL. The function is called with contextual keys and values
+ * (such as the cache name on cleanup or the actual key/value on put requests); must not be {@literal null}.
* @param batchStrategy must not be {@literal null}.
* @return new instance of {@link DefaultRedisCacheWriter}.
* @since 3.2
@@ -124,8 +123,8 @@ static RedisCacheWriter lockingRedisCacheWriter(RedisConnectionFactory connectio
byte[] get(String name, byte[] key);
/**
- * Get the binary value representation from Redis stored for the given key and set
- * the given {@link Duration TTL expiration} for the cache entry.
+ * Get the binary value representation from Redis stored for the given key and set the given {@link Duration TTL
+ * expiration} for the cache entry.
*
* @param name must not be {@literal null}.
* @param key must not be {@literal null}.
@@ -138,14 +137,41 @@ default byte[] get(String name, byte[] key, @Nullable Duration ttl) {
}
/**
- * Determines whether the asynchronous {@link #retrieve(String, byte[])}
- * and {@link #retrieve(String, byte[], Duration)} cache operations are supported by the implementation.
+ * Get the binary value representation from Redis stored for the given key and set the given {@link Duration TTL
+ * expiration} for the cache entry, obtaining the value from {@code valueLoader} if necessary.
*
- * The main factor for whether the {@literal retrieve} operation can be supported will primarily be determined
- * by the Redis driver in use at runtime.
+ * If possible (and configured for locking), implementations should ensure that the loading operation is synchronized
+ * so that the specified {@code valueLoader} is only called once in case of concurrent access on the same key.
+ *
+ * @param name must not be {@literal null}.
+ * @param key must not be {@literal null}.
+ * @param valueLoader value loader that creates the value if the cache lookup has been not successful.
+ * @param ttl {@link Duration} specifying the {@literal expiration timeout} for the cache entry.
+ * @param timeToIdleEnabled {@literal true} to enable Time to Idle when retrieving the value.
+ * @since 3.4
+ */
+ default byte[] get(String name, byte[] key, Supplier valueLoader, @Nullable Duration ttl,
+ boolean timeToIdleEnabled) {
+
+ byte[] bytes = timeToIdleEnabled ? get(name, key, ttl) : get(name, key);
+
+ if (bytes == null) {
+ bytes = valueLoader.get();
+ put(name, key, bytes, ttl);
+ }
+
+ return bytes;
+ }
+
+ /**
+ * Determines whether the asynchronous {@link #retrieve(String, byte[])} and
+ * {@link #retrieve(String, byte[], Duration)} cache operations are supported by the implementation.
+ *
+ * The main factor for whether the {@literal retrieve} operation can be supported will primarily be determined by the
+ * Redis driver in use at runtime.
*
- * Returns {@literal false} by default. This will have an effect of {@link RedisCache#retrieve(Object)}
- * and {@link RedisCache#retrieve(Object, Supplier)} throwing an {@link UnsupportedOperationException}.
+ * Returns {@literal false} by default. This will have an effect of {@link RedisCache#retrieve(Object)} and
+ * {@link RedisCache#retrieve(Object, Supplier)} throwing an {@link UnsupportedOperationException}.
*
* @return {@literal true} if asynchronous {@literal retrieve} operations are supported by the implementation.
* @since 3.2
@@ -155,14 +181,14 @@ default boolean supportsAsyncRetrieve() {
}
/**
- * Asynchronously retrieves the {@link CompletableFuture value} to which the {@link RedisCache}
- * maps the given {@link byte[] key}.
+ * Asynchronously retrieves the {@link CompletableFuture value} to which the {@link RedisCache} maps the given
+ * {@code byte[] key}.
*
* This operation is non-blocking.
*
* @param name {@link String} with the name of the {@link RedisCache}.
- * @param key {@link byte[] key} mapped to the {@link CompletableFuture value} in the {@link RedisCache}.
- * @return the {@link CompletableFuture value} to which the {@link RedisCache} maps the given {@link byte[] key}.
+ * @param key {@code byte[] key} mapped to the {@link CompletableFuture value} in the {@link RedisCache}.
+ * @return the {@link CompletableFuture value} to which the {@link RedisCache} maps the given {@code byte[] key}.
* @see #retrieve(String, byte[], Duration)
* @since 3.2
*/
@@ -171,15 +197,15 @@ default CompletableFuture retrieve(String name, byte[] key) {
}
/**
- * Asynchronously retrieves the {@link CompletableFuture value} to which the {@link RedisCache} maps
- * the given {@link byte[] key} setting the {@link Duration TTL expiration} for the cache entry.
+ * Asynchronously retrieves the {@link CompletableFuture value} to which the {@link RedisCache} maps the given
+ * {@code byte[] key} setting the {@link Duration TTL expiration} for the cache entry.
*
* This operation is non-blocking.
*
* @param name {@link String} with the name of the {@link RedisCache}.
- * @param key {@link byte[] key} mapped to the {@link CompletableFuture value} in the {@link RedisCache}.
+ * @param key {@code byte[] key} mapped to the {@link CompletableFuture value} in the {@link RedisCache}.
* @param ttl {@link Duration} specifying the {@literal expiration timeout} for the cache entry.
- * @return the {@link CompletableFuture value} to which the {@link RedisCache} maps the given {@link byte[] key}.
+ * @return the {@link CompletableFuture value} to which the {@link RedisCache} maps the given {@code byte[] key}.
* @since 3.2
*/
CompletableFuture retrieve(String name, byte[] key, @Nullable Duration ttl);
@@ -187,10 +213,10 @@ default CompletableFuture retrieve(String name, byte[] key) {
/**
* Write the given key/value pair to Redis and set the expiration time if defined.
*
- * @param name The cache name must not be {@literal null}.
- * @param key The key for the cache entry. Must not be {@literal null}.
- * @param value The value stored for the key. Must not be {@literal null}.
- * @param ttl Optional expiration time. Can be {@literal null}.
+ * @param name cache name must not be {@literal null}.
+ * @param key key for the cache entry. Must not be {@literal null}.
+ * @param value value stored for the key. Must not be {@literal null}.
+ * @param ttl optional expiration time. Can be {@literal null}.
*/
void put(String name, byte[] key, byte[] value, @Nullable Duration ttl);
@@ -199,10 +225,10 @@ default CompletableFuture retrieve(String name, byte[] key) {
*
* This operation is non-blocking.
*
- * @param name The cache name must not be {@literal null}.
- * @param key The key for the cache entry. Must not be {@literal null}.
- * @param value The value stored for the key. Must not be {@literal null}.
- * @param ttl Optional expiration time. Can be {@literal null}.
+ * @param name cache name must not be {@literal null}.
+ * @param key key for the cache entry. Must not be {@literal null}.
+ * @param value value stored for the key. Must not be {@literal null}.
+ * @param ttl optional expiration time. Can be {@literal null}.
* @since 3.2
*/
CompletableFuture store(String name, byte[] key, byte[] value, @Nullable Duration ttl);
@@ -210,10 +236,10 @@ default CompletableFuture retrieve(String name, byte[] key) {
/**
* Write the given value to Redis if the key does not already exist.
*
- * @param name The cache name must not be {@literal null}.
- * @param key The key for the cache entry. Must not be {@literal null}.
- * @param value The value stored for the key. Must not be {@literal null}.
- * @param ttl Optional expiration time. Can be {@literal null}.
+ * @param name cache name must not be {@literal null}.
+ * @param key key for the cache entry. Must not be {@literal null}.
+ * @param value value stored for the key. Must not be {@literal null}.
+ * @param ttl optional expiration time. Can be {@literal null}.
* @return {@literal null} if the value has been written, the value stored for the key if it already exists.
*/
@Nullable
@@ -222,16 +248,16 @@ default CompletableFuture retrieve(String name, byte[] key) {
/**
* Remove the given key from Redis.
*
- * @param name The cache name must not be {@literal null}.
- * @param key The key for the cache entry. Must not be {@literal null}.
+ * @param name cache name must not be {@literal null}.
+ * @param key key for the cache entry. Must not be {@literal null}.
*/
void remove(String name, byte[] key);
/**
* Remove all keys following the given pattern.
*
- * @param name The cache name must not be {@literal null}.
- * @param pattern The pattern for the keys to remove. Must not be {@literal null}.
+ * @param name cache name must not be {@literal null}.
+ * @param pattern pattern for the keys to remove. Must not be {@literal null}.
*/
void clean(String name, byte[] pattern);
@@ -264,8 +290,8 @@ interface TtlFunction {
/**
* Creates a {@literal Singleton} {@link TtlFunction} using the given {@link Duration}.
*
- * @param duration the time to live. Can be {@link Duration#ZERO} for persistent values (i.e. cache entry
- * does not expire).
+ * @param duration the time to live. Can be {@link Duration#ZERO} for persistent values (i.e. cache entry does not
+ * expire).
* @return a singleton {@link TtlFunction} using {@link Duration}.
*/
static TtlFunction just(Duration duration) {
@@ -293,7 +319,7 @@ static TtlFunction persistent() {
* persistent value that does not expire.
*
* @param key the cache key.
- * @param value the cache value. Can be {@code null} if the cache supports {@code null} value caching.
+ * @param value the cache value. Can be {@literal null} if the cache supports {@literal null} value caching.
* @return the computed {@link Duration time-to-live (TTL)}. Can be {@link Duration#ZERO} for persistent values
* (i.e. cache entry does not expire).
*/
diff --git a/src/main/java/org/springframework/data/redis/config/RedisCollectionParser.java b/src/main/java/org/springframework/data/redis/config/RedisCollectionParser.java
index 6e5963eb9f..bb50defa3e 100644
--- a/src/main/java/org/springframework/data/redis/config/RedisCollectionParser.java
+++ b/src/main/java/org/springframework/data/redis/config/RedisCollectionParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/config/RedisListenerContainerParser.java b/src/main/java/org/springframework/data/redis/config/RedisListenerContainerParser.java
index 7c09a8896d..c69d645e7b 100644
--- a/src/main/java/org/springframework/data/redis/config/RedisListenerContainerParser.java
+++ b/src/main/java/org/springframework/data/redis/config/RedisListenerContainerParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/config/RedisNamespaceHandler.java b/src/main/java/org/springframework/data/redis/config/RedisNamespaceHandler.java
index 256dba0ffd..5c4394020a 100644
--- a/src/main/java/org/springframework/data/redis/config/RedisNamespaceHandler.java
+++ b/src/main/java/org/springframework/data/redis/config/RedisNamespaceHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/AbstractRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/AbstractRedisConnection.java
index 7b2581973d..46a7a12ded 100644
--- a/src/main/java/org/springframework/data/redis/connection/AbstractRedisConnection.java
+++ b/src/main/java/org/springframework/data/redis/connection/AbstractRedisConnection.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014-2024 the original author or authors.
+ * Copyright 2014-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/BitFieldSubCommands.java b/src/main/java/org/springframework/data/redis/connection/BitFieldSubCommands.java
index bb24d05634..b90121f90b 100644
--- a/src/main/java/org/springframework/data/redis/connection/BitFieldSubCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/BitFieldSubCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-2024 the original author or authors.
+ * Copyright 2018-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -151,10 +151,9 @@ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
- if (!(o instanceof BitFieldSubCommands)) {
+ if (!(o instanceof BitFieldSubCommands that)) {
return false;
}
- BitFieldSubCommands that = (BitFieldSubCommands) o;
return ObjectUtils.nullSafeEquals(subCommands, that.subCommands);
}
@@ -437,10 +436,9 @@ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
- if (!(o instanceof Offset)) {
+ if (!(o instanceof Offset that)) {
return false;
}
- Offset that = (Offset) o;
if (offset != that.offset) {
return false;
}
@@ -549,10 +547,9 @@ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
- if (!(o instanceof BitFieldType)) {
+ if (!(o instanceof BitFieldType that)) {
return false;
}
- BitFieldType that = (BitFieldType) o;
if (signed != that.signed) {
return false;
}
@@ -597,10 +594,9 @@ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
- if (!(o instanceof AbstractBitFieldSubCommand)) {
+ if (!(o instanceof AbstractBitFieldSubCommand that)) {
return false;
}
- AbstractBitFieldSubCommand that = (AbstractBitFieldSubCommand) o;
if (!ObjectUtils.nullSafeEquals(getClass(), that.getClass())) {
return false;
}
@@ -648,13 +644,13 @@ public static class BitFieldSet extends AbstractBitFieldSubCommand {
* @return
* @since 2.5.2
*/
- public static BitFieldSet create(BitFieldType type,Offset offset,long value){
+ public static BitFieldSet create(BitFieldType type, Offset offset, long value) {
Assert.notNull(type, "BitFieldType must not be null");
Assert.notNull(offset, "Offset must not be null");
BitFieldSet instance = new BitFieldSet();
- instance.type = type;
+ instance.type = type;
instance.offset = offset;
instance.value = value;
@@ -680,13 +676,12 @@ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
- if (!(o instanceof BitFieldSet)) {
+ if (!(o instanceof BitFieldSet that)) {
return false;
}
if (!super.equals(o)) {
return false;
}
- BitFieldSet that = (BitFieldSet) o;
if (value != that.value) {
return false;
}
@@ -728,13 +723,13 @@ public static class BitFieldGet extends AbstractBitFieldSubCommand {
* @since 2.5.2
* @return
*/
- public static BitFieldGet create(BitFieldType type,Offset offset){
+ public static BitFieldGet create(BitFieldType type, Offset offset) {
Assert.notNull(type, "BitFieldType must not be null");
Assert.notNull(offset, "Offset must not be null");
BitFieldGet instance = new BitFieldGet();
- instance.type = type;
+ instance.type = type;
instance.offset = offset;
return instance;
@@ -767,7 +762,7 @@ public static class BitFieldIncrBy extends AbstractBitFieldSubCommand {
* @return
* @since 2.5.2
*/
- public static BitFieldIncrBy create(BitFieldType type,Offset offset,long value){
+ public static BitFieldIncrBy create(BitFieldType type, Offset offset, long value) {
return create(type, offset, value, null);
}
@@ -787,7 +782,7 @@ public static BitFieldIncrBy create(BitFieldType type, Offset offset, long value
Assert.notNull(offset, "Offset must not be null");
BitFieldIncrBy instance = new BitFieldIncrBy();
- instance.type = type;
+ instance.type = type;
instance.offset = offset;
instance.value = value;
instance.overflow = overflow;
@@ -832,10 +827,9 @@ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
- if (!(o instanceof BitFieldIncrBy)) {
+ if (!(o instanceof BitFieldIncrBy that)) {
return false;
}
- BitFieldIncrBy that = (BitFieldIncrBy) o;
if (value != that.value) {
return false;
}
diff --git a/src/main/java/org/springframework/data/redis/connection/ClusterCommandExecutionFailureException.java b/src/main/java/org/springframework/data/redis/connection/ClusterCommandExecutionFailureException.java
index 7a786b63f9..1ee5458ec3 100644
--- a/src/main/java/org/springframework/data/redis/connection/ClusterCommandExecutionFailureException.java
+++ b/src/main/java/org/springframework/data/redis/connection/ClusterCommandExecutionFailureException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2024 the original author or authors.
+ * Copyright 2015-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
*/
package org.springframework.data.redis.connection;
+import java.io.Serial;
import java.util.Collections;
import java.util.List;
@@ -29,7 +30,7 @@
*/
public class ClusterCommandExecutionFailureException extends UncategorizedDataAccessException {
- private static final long serialVersionUID = 5727044227040368955L;
+ @Serial private static final long serialVersionUID = 5727044227040368955L;
/**
* Creates new {@link ClusterCommandExecutionFailureException}.
diff --git a/src/main/java/org/springframework/data/redis/connection/ClusterCommandExecutor.java b/src/main/java/org/springframework/data/redis/connection/ClusterCommandExecutor.java
index 3e896e53d9..b0d9c1a720 100644
--- a/src/main/java/org/springframework/data/redis/connection/ClusterCommandExecutor.java
+++ b/src/main/java/org/springframework/data/redis/connection/ClusterCommandExecutor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2024 the original author or authors.
+ * Copyright 2015-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -130,12 +130,9 @@ private NodeResult executeCommandOnSingleNode(ClusterCommandCallback this.maxRedirects) {
-
- String message = String.format("Cannot follow Cluster Redirects over more than %s legs; "
- + "Consider increasing the number of redirects to follow; Current value is: %s.",
- redirectCount, this.maxRedirects);
-
- throw new TooManyClusterRedirectionsException(message);
+ throw new TooManyClusterRedirectionsException(("Cannot follow Cluster Redirects over more than %s legs;"
+ + " Consider increasing the number of redirects to follow; Current value is: %s")
+ .formatted(redirectCount, this.maxRedirects));
}
RedisClusterNode nodeToUse = lookupNode(node);
@@ -178,7 +175,7 @@ private RedisClusterNode lookupNode(RedisClusterNode node) {
try {
return topologyProvider.getTopology().lookup(node);
} catch (ClusterStateFailureException ex) {
- throw new IllegalArgumentException(String.format("Node %s is unknown to cluster", node), ex);
+ throw new IllegalArgumentException("Node %s is unknown to cluster".formatted(node), ex);
}
}
@@ -215,7 +212,7 @@ public MultiNodeResult executeCommandAsyncOnNodes(ClusterCommandCallba
try {
resolvedRedisClusterNodes.add(topology.lookup(node));
} catch (ClusterStateFailureException ex) {
- throw new IllegalArgumentException(String.format("Node %s is unknown to cluster", node), ex);
+ throw new IllegalArgumentException("Node %s is unknown to cluster".formatted(node), ex);
}
}
@@ -474,9 +471,9 @@ public RedisClusterNode getNode() {
}
/**
- * Return the {@link byte[] key} mapped to the value stored in Redis.
+ * Return the {@code byte[] key} mapped to the value stored in Redis.
*
- * @return a {@link byte[] byte array} of the key mapped to the value stored in Redis.
+ * @return a {@code byte[] byte array} of the key mapped to the value stored in Redis.
*/
public byte[] getKey() {
return this.key.getArray();
diff --git a/src/main/java/org/springframework/data/redis/connection/ClusterInfo.java b/src/main/java/org/springframework/data/redis/connection/ClusterInfo.java
index e09ebbfd8d..8d6179811c 100644
--- a/src/main/java/org/springframework/data/redis/connection/ClusterInfo.java
+++ b/src/main/java/org/springframework/data/redis/connection/ClusterInfo.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2024 the original author or authors.
+ * Copyright 2015-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ClusterNodeResourceProvider.java b/src/main/java/org/springframework/data/redis/connection/ClusterNodeResourceProvider.java
index 454ac41d23..a12ce3f8ea 100644
--- a/src/main/java/org/springframework/data/redis/connection/ClusterNodeResourceProvider.java
+++ b/src/main/java/org/springframework/data/redis/connection/ClusterNodeResourceProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2024 the original author or authors.
+ * Copyright 2015-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ClusterSlotHashUtil.java b/src/main/java/org/springframework/data/redis/connection/ClusterSlotHashUtil.java
index 6494f423c7..58b49520bf 100644
--- a/src/main/java/org/springframework/data/redis/connection/ClusterSlotHashUtil.java
+++ b/src/main/java/org/springframework/data/redis/connection/ClusterSlotHashUtil.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2024 the original author or authors.
+ * Copyright 2015-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ClusterTopology.java b/src/main/java/org/springframework/data/redis/connection/ClusterTopology.java
index bb82facae2..42436431f4 100644
--- a/src/main/java/org/springframework/data/redis/connection/ClusterTopology.java
+++ b/src/main/java/org/springframework/data/redis/connection/ClusterTopology.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2024 the original author or authors.
+ * Copyright 2015-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -141,7 +141,7 @@ public RedisClusterNode getKeyServingMasterNode(byte[] key) {
}
throw new ClusterStateFailureException(
- String.format("Could not find master node serving slot %s for key '%s',", slot, Arrays.toString(key)));
+ "Could not find master node serving slot %s for key '%s',".formatted(slot, Arrays.toString(key)));
}
/**
@@ -161,7 +161,7 @@ public RedisClusterNode lookup(String host, int port) {
}
throw new ClusterStateFailureException(
- String.format("Could not find node at %s:%s; Is your cluster info up to date", host, port));
+ "Could not find node at %s:%d; Is your cluster info up to date".formatted(host, port));
}
/**
@@ -182,7 +182,7 @@ public RedisClusterNode lookup(String nodeId) {
}
throw new ClusterStateFailureException(
- String.format("Could not find node at %s; Is your cluster info up to date", nodeId));
+ "Could not find node at %s; Is your cluster info up to date".formatted(nodeId));
}
/**
@@ -210,7 +210,7 @@ public RedisClusterNode lookup(RedisClusterNode node) {
}
throw new ClusterStateFailureException(
- String.format("Could not find node at %s; Have you provided either host and port or the nodeId", node));
+ ("Could not find node at %s;" + " Have you provided either host and port or the nodeId").formatted(node));
}
/**
diff --git a/src/main/java/org/springframework/data/redis/connection/ClusterTopologyProvider.java b/src/main/java/org/springframework/data/redis/connection/ClusterTopologyProvider.java
index 22827c1d2b..f03067e21f 100644
--- a/src/main/java/org/springframework/data/redis/connection/ClusterTopologyProvider.java
+++ b/src/main/java/org/springframework/data/redis/connection/ClusterTopologyProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2024 the original author or authors.
+ * Copyright 2015-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ConnectionUtils.java b/src/main/java/org/springframework/data/redis/connection/ConnectionUtils.java
index 9c768307f4..58af2c6ba1 100644
--- a/src/main/java/org/springframework/data/redis/connection/ConnectionUtils.java
+++ b/src/main/java/org/springframework/data/redis/connection/ConnectionUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/DataType.java b/src/main/java/org/springframework/data/redis/connection/DataType.java
index 746eb09a9b..1b0635fbeb 100644
--- a/src/main/java/org/springframework/data/redis/connection/DataType.java
+++ b/src/main/java/org/springframework/data/redis/connection/DataType.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/DecoratedRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DecoratedRedisConnection.java
index 2757810c0e..19ad271389 100644
--- a/src/main/java/org/springframework/data/redis/connection/DecoratedRedisConnection.java
+++ b/src/main/java/org/springframework/data/redis/connection/DecoratedRedisConnection.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultMessage.java b/src/main/java/org/springframework/data/redis/connection/DefaultMessage.java
index 0ec00955e9..b96a0895f3 100644
--- a/src/main/java/org/springframework/data/redis/connection/DefaultMessage.java
+++ b/src/main/java/org/springframework/data/redis/connection/DefaultMessage.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultSortParameters.java b/src/main/java/org/springframework/data/redis/connection/DefaultSortParameters.java
index 44526bd999..bd50f34ba6 100644
--- a/src/main/java/org/springframework/data/redis/connection/DefaultSortParameters.java
+++ b/src/main/java/org/springframework/data/redis/connection/DefaultSortParameters.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java
index 80cc128e55..c2ea198d8b 100644
--- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java
+++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
@@ -34,10 +35,19 @@
import org.springframework.data.redis.connection.convert.ListConverter;
import org.springframework.data.redis.connection.convert.MapConverter;
import org.springframework.data.redis.connection.convert.SetConverter;
-import org.springframework.data.redis.connection.stream.*;
+import org.springframework.data.redis.connection.stream.ByteRecord;
+import org.springframework.data.redis.connection.stream.Consumer;
+import org.springframework.data.redis.connection.stream.MapRecord;
+import org.springframework.data.redis.connection.stream.PendingMessages;
+import org.springframework.data.redis.connection.stream.PendingMessagesSummary;
+import org.springframework.data.redis.connection.stream.ReadOffset;
+import org.springframework.data.redis.connection.stream.RecordId;
import org.springframework.data.redis.connection.stream.StreamInfo.XInfoConsumers;
import org.springframework.data.redis.connection.stream.StreamInfo.XInfoGroups;
import org.springframework.data.redis.connection.stream.StreamInfo.XInfoStream;
+import org.springframework.data.redis.connection.stream.StreamOffset;
+import org.springframework.data.redis.connection.stream.StreamReadOptions;
+import org.springframework.data.redis.connection.stream.StringRecord;
import org.springframework.data.redis.connection.zset.Aggregate;
import org.springframework.data.redis.connection.zset.DefaultTuple;
import org.springframework.data.redis.connection.zset.Tuple;
@@ -348,13 +358,13 @@ public Long exists(byte[]... keys) {
}
@Override
- public Boolean expire(byte[] key, long seconds) {
- return convertAndReturn(delegate.expire(key, seconds), Converters.identityConverter());
+ public Boolean expire(byte[] key, long seconds, ExpirationOptions.Condition condition) {
+ return convertAndReturn(delegate.expire(key, seconds, condition), Converters.identityConverter());
}
@Override
- public Boolean expireAt(byte[] key, long unixTime) {
- return convertAndReturn(delegate.expireAt(key, unixTime), Converters.identityConverter());
+ public Boolean expireAt(byte[] key, long unixTime, ExpirationOptions.Condition condition) {
+ return convertAndReturn(delegate.expireAt(key, unixTime, condition), Converters.identityConverter());
}
@Override
@@ -770,6 +780,11 @@ public Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption op
return convertAndReturn(delegate.set(key, value, expiration, option), Converters.identityConverter());
}
+ @Override
+ public byte[] setGet(byte[] key, byte[] value, Expiration expiration, SetOption option) {
+ return convertAndReturn(delegate.setGet(key, value, expiration, option), Converters.identityConverter());
+ }
+
@Override
public Boolean setBit(byte[] key, long offset, boolean value) {
return convertAndReturn(delegate.setBit(key, offset, value), Converters.identityConverter());
@@ -1292,13 +1307,13 @@ public Long zLexCount(String key, org.springframework.data.domain.Range
}
@Override
- public Boolean pExpire(byte[] key, long millis) {
- return convertAndReturn(delegate.pExpire(key, millis), Converters.identityConverter());
+ public Boolean pExpire(byte[] key, long millis, ExpirationOptions.Condition condition) {
+ return convertAndReturn(delegate.pExpire(key, millis, condition), Converters.identityConverter());
}
@Override
- public Boolean pExpireAt(byte[] key, long unixTimeInMillis) {
- return convertAndReturn(delegate.pExpireAt(key, unixTimeInMillis), Converters.identityConverter());
+ public Boolean pExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition) {
+ return convertAndReturn(delegate.pExpireAt(key, unixTimeInMillis, condition), Converters.identityConverter());
}
@Override
@@ -1481,13 +1496,13 @@ public Boolean exists(String key) {
}
@Override
- public Boolean expire(String key, long seconds) {
- return expire(serialize(key), seconds);
+ public Boolean expire(String key, long seconds, ExpirationOptions.Condition condition) {
+ return expire(serialize(key), seconds, condition);
}
@Override
- public Boolean expireAt(String key, long unixTime) {
- return expireAt(serialize(key), unixTime);
+ public Boolean expireAt(String key, long unixTime, ExpirationOptions.Condition condition) {
+ return expireAt(serialize(key), unixTime, condition);
}
@Override
@@ -2475,13 +2490,13 @@ public Object execute(String command, String... args) {
}
@Override
- public Boolean pExpire(String key, long millis) {
- return pExpire(serialize(key), millis);
+ public Boolean pExpire(String key, long millis, ExpirationOptions.Condition condition) {
+ return pExpire(serialize(key), millis, condition);
}
@Override
- public Boolean pExpireAt(String key, long unixTimeInMillis) {
- return pExpireAt(serialize(key), unixTimeInMillis);
+ public Boolean pExpireAt(String key, long unixTimeInMillis, ExpirationOptions.Condition condition) {
+ return pExpireAt(serialize(key), unixTimeInMillis, condition);
}
@Override
@@ -2560,12 +2575,105 @@ public Cursor> hScan(byte[] key, ScanOptions options) {
return this.delegate.hScan(key, options);
}
- @Nullable
@Override
public Long hStrLen(byte[] key, byte[] field) {
return convertAndReturn(delegate.hStrLen(key, field), Converters.identityConverter());
}
+ public @Nullable List applyHashFieldExpiration(byte[] key,
+ org.springframework.data.redis.core.types.Expiration expiration,
+ ExpirationOptions options, byte[]... fields) {
+ return this.delegate.applyHashFieldExpiration(key, expiration, options, fields);
+ }
+
+ @Override
+ public List hExpire(byte[] key, long seconds, ExpirationOptions.Condition condition, byte[]... fields) {
+ return this.delegate.hExpire(key, seconds, condition, fields);
+ }
+
+ @Override
+ public List hpExpire(byte[] key, long millis, ExpirationOptions.Condition condition, byte[]... fields) {
+ return this.delegate.hpExpire(key, millis, condition, fields);
+ }
+
+ @Override
+ public List hExpireAt(byte[] key, long unixTime, ExpirationOptions.Condition condition, byte[]... fields) {
+ return this.delegate.hExpireAt(key, unixTime, condition, fields);
+ }
+
+ @Override
+ public List hpExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition,
+ byte[]... fields) {
+ return this.delegate.hpExpireAt(key, unixTimeInMillis, condition, fields);
+ }
+
+ @Override
+ public List hPersist(byte[] key, byte[]... fields) {
+ return this.delegate.hPersist(key, fields);
+ }
+
+ @Override
+ public List hTtl(byte[] key, byte[]... fields) {
+ return this.delegate.hTtl(key, fields);
+ }
+
+ @Override
+ public List hpTtl(byte[] key, byte[]... fields) {
+ return this.delegate.hpTtl(key, fields);
+ }
+
+ @Override
+ public List hTtl(byte[] key, TimeUnit timeUnit, byte[]... fields) {
+ return this.delegate.hTtl(key, timeUnit, fields);
+ }
+
+ public @Nullable List applyExpiration(String key,
+ org.springframework.data.redis.core.types.Expiration expiration,
+ ExpirationOptions options, String... fields) {
+ return this.applyHashFieldExpiration(serialize(key), expiration, options, serializeMulti(fields));
+ }
+
+ @Override
+ public List hExpire(String key, long seconds, ExpirationOptions.Condition condition, String... fields) {
+ return hExpire(serialize(key), seconds, condition, serializeMulti(fields));
+ }
+
+ @Override
+ public List hpExpire(String key, long millis, ExpirationOptions.Condition condition, String... fields) {
+ return hpExpire(serialize(key), millis, condition, serializeMulti(fields));
+ }
+
+ @Override
+ public List hExpireAt(String key, long unixTime, ExpirationOptions.Condition condition, String... fields) {
+ return hExpireAt(serialize(key), unixTime, condition, serializeMulti(fields));
+ }
+
+ @Override
+ public List hpExpireAt(String key, long unixTimeInMillis, ExpirationOptions.Condition condition,
+ String... fields) {
+ return hpExpireAt(serialize(key), unixTimeInMillis, condition, serializeMulti(fields));
+ }
+
+ @Override
+ public List hPersist(String key, String... fields) {
+ return hPersist(serialize(key), serializeMulti(fields));
+ }
+
+ @Override
+ public List hTtl(String key, String... fields) {
+ return hTtl(serialize(key), serializeMulti(fields));
+ }
+
+ @Override
+ public List hTtl(String key, TimeUnit timeUnit, String... fields) {
+ return hTtl(serialize(key), timeUnit, serializeMulti(fields));
+ }
+
+ @Override
+ public List hpTtl(String key, String... fields) {
+ return hTtl(serialize(key), serializeMulti(fields));
+ }
+
@Override
public void setClientName(byte[] name) {
this.delegate.setClientName(name);
diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultStringTuple.java b/src/main/java/org/springframework/data/redis/connection/DefaultStringTuple.java
index 7851a0f2a8..b35522c642 100644
--- a/src/main/java/org/springframework/data/redis/connection/DefaultStringTuple.java
+++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringTuple.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisClusterConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisClusterConnection.java
index dfa5986df5..369a272476 100644
--- a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisClusterConnection.java
+++ b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisClusterConnection.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2024 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java
index aaeaafe18b..f3be7ab276 100644
--- a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java
+++ b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2024 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -65,6 +65,7 @@
* @author ihaohong
* @author Dennis Neufeld
* @author Shyngys Sapraliyev
+ * @author Tihomir Mateev
* @since 2.0
*/
@Deprecated
@@ -160,7 +161,14 @@ default Boolean renameNX(byte[] sourceKey, byte[] targetKey) {
@Override
@Deprecated
default Boolean expire(byte[] key, long seconds) {
- return keyCommands().expire(key, seconds);
+ return keyCommands().expire(key, seconds, ExpirationOptions.Condition.ALWAYS);
+ }
+
+ /** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
+ @Override
+ @Deprecated
+ default Boolean expire(byte[] key, long seconds, ExpirationOptions.Condition condition) {
+ return keyCommands().expire(key, seconds, condition);
}
/** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
@@ -202,21 +210,42 @@ default Long pTtl(byte[] key, TimeUnit timeUnit) {
@Override
@Deprecated
default Boolean pExpire(byte[] key, long millis) {
- return keyCommands().pExpire(key, millis);
+ return keyCommands().pExpire(key, millis, ExpirationOptions.Condition.ALWAYS);
+ }
+
+ /** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
+ @Override
+ @Deprecated
+ default Boolean pExpire(byte[] key, long millis, ExpirationOptions.Condition condition) {
+ return keyCommands().pExpire(key, millis, condition);
}
/** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
@Override
@Deprecated
default Boolean pExpireAt(byte[] key, long unixTimeInMillis) {
- return keyCommands().pExpireAt(key, unixTimeInMillis);
+ return keyCommands().pExpireAt(key, unixTimeInMillis, ExpirationOptions.Condition.ALWAYS);
+ }
+
+ /** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
+ @Override
+ @Deprecated
+ default Boolean pExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition) {
+ return keyCommands().pExpireAt(key, unixTimeInMillis, condition);
}
/** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
@Override
@Deprecated
default Boolean expireAt(byte[] key, long unixTime) {
- return keyCommands().expireAt(key, unixTime);
+ return keyCommands().expireAt(key, unixTime, ExpirationOptions.Condition.ALWAYS);
+ }
+
+ /** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
+ @Override
+ @Deprecated
+ default Boolean expireAt(byte[] key, long unixTime, ExpirationOptions.Condition condition) {
+ return keyCommands().expireAt(key, unixTime, condition);
}
/** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
@@ -326,6 +355,13 @@ default Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption o
return stringCommands().set(key, value, expiration, option);
}
+ /** @deprecated in favor of {@link RedisConnection#stringCommands()}}. */
+ @Override
+ @Deprecated
+ default byte[] setGet(byte[] key, byte[] value, Expiration expiration, SetOption option) {
+ return stringCommands().setGet(key, value, expiration, option);
+ }
+
/** @deprecated in favor of {@link RedisConnection#stringCommands()}}. */
@Override
@Deprecated
@@ -1470,6 +1506,101 @@ default Long hStrLen(byte[] key, byte[] field) {
return hashCommands().hStrLen(key, field);
}
+ /** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
+ @Override
+ @Deprecated
+ default List hExpire(byte[] key, long seconds, byte[]... fields) {
+ return hashCommands().hExpire(key, seconds, ExpirationOptions.Condition.ALWAYS, fields);
+ }
+
+ /** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
+ @Override
+ @Deprecated
+ default List hExpire(byte[] key, long seconds, ExpirationOptions.Condition condition, byte[]... fields) {
+ return hashCommands().hExpire(key, seconds, condition, fields);
+ }
+
+ /** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
+ @Override
+ @Deprecated
+ default List hpExpire(byte[] key, long millis, byte[]... fields) {
+ return hashCommands().hpExpire(key, millis, ExpirationOptions.Condition.ALWAYS, fields);
+ }
+
+ /** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
+ @Override
+ @Deprecated
+ default List hpExpire(byte[] key, long millis, ExpirationOptions.Condition condition, byte[]... fields) {
+ return hashCommands().hpExpire(key, millis, condition, fields);
+ }
+
+ /** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
+ @Override
+ @Deprecated
+ default List hExpireAt(byte[] key, long unixTime, byte[]... fields) {
+ return hashCommands().hExpireAt(key, unixTime, ExpirationOptions.Condition.ALWAYS, fields);
+ }
+
+ /** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
+ @Override
+ @Deprecated
+ default List hExpireAt(byte[] key, long unixTime, ExpirationOptions.Condition condition,
+ byte[]... fields) {
+ return hashCommands().hExpireAt(key, unixTime, condition, fields);
+ }
+
+ /** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
+ @Override
+ @Deprecated
+ default List hpExpireAt(byte[] key, long unixTimeInMillis, byte[]... fields) {
+ return hashCommands().hpExpireAt(key, unixTimeInMillis, ExpirationOptions.Condition.ALWAYS, fields);
+ }
+
+ /** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
+ @Override
+ @Deprecated
+ default List hpExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition,
+ byte[]... fields) {
+ return hashCommands().hpExpireAt(key, unixTimeInMillis, condition, fields);
+ }
+
+ /** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
+ @Override
+ @Deprecated
+ default List hPersist(byte[] key, byte[]... fields) {
+ return hashCommands().hPersist(key, fields);
+ }
+
+ /** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
+ @Override
+ @Deprecated
+ default List hTtl(byte[] key, byte[]... fields) {
+ return hashCommands().hTtl(key, fields);
+ }
+
+ /** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
+ @Override
+ @Deprecated
+ default List hTtl(byte[] key, TimeUnit timeUnit, byte[]... fields) {
+ return hashCommands().hTtl(key, timeUnit, fields);
+ }
+
+ /** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
+ @Override
+ @Deprecated
+ default List hpTtl(byte[] key, byte[]... fields) {
+ return hashCommands().hpTtl(key, fields);
+ }
+
+ /** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
+ @Override
+ @Deprecated
+ default @Nullable List applyHashFieldExpiration(byte[] key,
+ org.springframework.data.redis.core.types.Expiration expiration, ExpirationOptions options,
+ byte[]... fields) {
+ return hashCommands().applyHashFieldExpiration(key, expiration, options, fields);
+ }
+
// GEO COMMANDS
/** @deprecated in favor of {@link RedisConnection#geoCommands()}}. */
@@ -1841,9 +1972,8 @@ default T evalSha(byte[] scriptSha, ReturnType returnType, int numKeys, byte
/** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */
@Override
@Deprecated
- default Long zRangeStoreByLex(byte[] dstKey, byte[] srcKey,
- org.springframework.data.domain.Range range,
- org.springframework.data.redis.connection.Limit limit) {
+ default Long zRangeStoreByLex(byte[] dstKey, byte[] srcKey, org.springframework.data.domain.Range range,
+ org.springframework.data.redis.connection.Limit limit) {
return zSetCommands().zRangeStoreByLex(dstKey, srcKey, range, limit);
}
@@ -1860,7 +1990,7 @@ default Long zRangeStoreRevByLex(byte[] dstKey, byte[] srcKey, org.springframewo
@Deprecated
default Long zRangeStoreByScore(byte[] dstKey, byte[] srcKey,
org.springframework.data.domain.Range extends Number> range,
- org.springframework.data.redis.connection.Limit limit) {
+ org.springframework.data.redis.connection.Limit limit) {
return zSetCommands().zRangeStoreByScore(dstKey, srcKey, range, limit);
}
diff --git a/src/main/java/org/springframework/data/redis/connection/ExpirationOptions.java b/src/main/java/org/springframework/data/redis/connection/ExpirationOptions.java
new file mode 100644
index 0000000000..900f27bd9d
--- /dev/null
+++ b/src/main/java/org/springframework/data/redis/connection/ExpirationOptions.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.redis.connection;
+
+import java.util.Objects;
+
+import org.springframework.lang.Contract;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Expiration options for Expiation updates.
+ *
+ * @author Christoph Strobl
+ * @author Mark Paluch
+ * @since 3.5
+ */
+public class ExpirationOptions {
+
+ private static final ExpirationOptions NONE = new ExpirationOptions(Condition.ALWAYS);
+ private final Condition condition;
+
+ ExpirationOptions(Condition condition) {
+ this.condition = condition;
+ }
+
+ /**
+ * @return an empty expiration options object.
+ */
+ public static ExpirationOptions none() {
+ return NONE;
+ }
+
+ /**
+ * @return builder for creating {@code FieldExpireOptionsBuilder}.
+ */
+ public static ExpirationOptionsBuilder builder() {
+ return new ExpirationOptionsBuilder();
+ }
+
+ public Condition getCondition() {
+ return condition;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ExpirationOptions that = (ExpirationOptions) o;
+ return ObjectUtils.nullSafeEquals(this.condition, that.condition);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(condition);
+ }
+
+ /**
+ * Builder to build {@link ExpirationOptions}
+ */
+ public static class ExpirationOptionsBuilder {
+
+ private Condition condition = Condition.ALWAYS;
+
+ private ExpirationOptionsBuilder() {}
+
+ /**
+ * Apply to fields that have no expiration.
+ */
+ @Contract("-> this")
+ public ExpirationOptionsBuilder nx() {
+ this.condition = Condition.NX;
+ return this;
+ }
+
+ /**
+ * Apply to fields that have an existing expiration.
+ */
+ @Contract("-> this")
+ public ExpirationOptionsBuilder xx() {
+ this.condition = Condition.XX;
+ return this;
+ }
+
+ /**
+ * Apply to fields when the new expiration is greater than the current one.
+ */
+ @Contract("-> this")
+ public ExpirationOptionsBuilder gt() {
+ this.condition = Condition.GT;
+ return this;
+ }
+
+ /**
+ * Apply to fields when the new expiration is lower than the current one.
+ */
+ @Contract("-> this")
+ public ExpirationOptionsBuilder lt() {
+ this.condition = Condition.LT;
+ return this;
+ }
+
+ public ExpirationOptions build() {
+ return condition == Condition.ALWAYS ? NONE : new ExpirationOptions(condition);
+ }
+
+ }
+
+ /**
+ * Conditions to apply when changing expiration.
+ */
+ public enum Condition {
+
+ /**
+ * Always apply expiration.
+ */
+ ALWAYS,
+
+ /**
+ * Set expiration only when the field has no expiration.
+ */
+ NX,
+
+ /**
+ * Set expiration only when the field has an existing expiration.
+ */
+ XX,
+
+ /**
+ * Set expiration only when the new expiration is greater than current one.
+ */
+ GT,
+
+ /**
+ * Set expiration only when the new expiration is greater than current one.
+ */
+ LT
+
+ }
+
+}
diff --git a/src/main/java/org/springframework/data/redis/connection/FutureResult.java b/src/main/java/org/springframework/data/redis/connection/FutureResult.java
index 122e889798..58da880234 100644
--- a/src/main/java/org/springframework/data/redis/connection/FutureResult.java
+++ b/src/main/java/org/springframework/data/redis/connection/FutureResult.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2024 the original author or authors.
+ * Copyright 2013-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/Limit.java b/src/main/java/org/springframework/data/redis/connection/Limit.java
index f47e2b6747..d6abf62b85 100644
--- a/src/main/java/org/springframework/data/redis/connection/Limit.java
+++ b/src/main/java/org/springframework/data/redis/connection/Limit.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022-2024 the original author or authors.
+ * Copyright 2022-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/Message.java b/src/main/java/org/springframework/data/redis/connection/Message.java
index 5be91f3897..138f61f3b2 100644
--- a/src/main/java/org/springframework/data/redis/connection/Message.java
+++ b/src/main/java/org/springframework/data/redis/connection/Message.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/MessageListener.java b/src/main/java/org/springframework/data/redis/connection/MessageListener.java
index fcf894a413..4857b45c6d 100644
--- a/src/main/java/org/springframework/data/redis/connection/MessageListener.java
+++ b/src/main/java/org/springframework/data/redis/connection/MessageListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/NamedNode.java b/src/main/java/org/springframework/data/redis/connection/NamedNode.java
index a638ed5781..5ccf100762 100644
--- a/src/main/java/org/springframework/data/redis/connection/NamedNode.java
+++ b/src/main/java/org/springframework/data/redis/connection/NamedNode.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014-2024 the original author or authors.
+ * Copyright 2014-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/PoolException.java b/src/main/java/org/springframework/data/redis/connection/PoolException.java
index 06969e9ace..1dc6b89cbb 100644
--- a/src/main/java/org/springframework/data/redis/connection/PoolException.java
+++ b/src/main/java/org/springframework/data/redis/connection/PoolException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2024 the original author or authors.
+ * Copyright 2013-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterCommands.java
index da4fad9d76..d780acced3 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020-2024 the original author or authors.
+ * Copyright 2020-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterGeoCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterGeoCommands.java
index d4496375cd..ae97652ba0 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterGeoCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterGeoCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterHashCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterHashCommands.java
index 60e2390988..6674520b33 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterHashCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterHashCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterHyperLogLogCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterHyperLogLogCommands.java
index 03df631960..2623eeda84 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterHyperLogLogCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterHyperLogLogCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterKeyCommands.java
index 21aea13ba1..d650b738eb 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterKeyCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterKeyCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterListCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterListCommands.java
index ad5bf5a3dc..f331728178 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterListCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterListCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterNumberCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterNumberCommands.java
index 5968745c1c..3840a33b32 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterNumberCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterNumberCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterScriptingCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterScriptingCommands.java
index b027421546..a7e0f171c7 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterScriptingCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterScriptingCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2024 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterServerCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterServerCommands.java
index 050715b734..2a19d5fcfe 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterServerCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterServerCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2024 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterSetCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterSetCommands.java
index 8590edeab9..b20809fd24 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterSetCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterSetCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterStreamCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterStreamCommands.java
index 696958fd77..d2dacc7fad 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterStreamCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterStreamCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-2024 the original author or authors.
+ * Copyright 2018-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterStringCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterStringCommands.java
index 3800aec877..a410627a3a 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterStringCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterStringCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterZSetCommands.java
index f11a90d7cb..167d0f59b1 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterZSetCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterZSetCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveGeoCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveGeoCommands.java
index cbeec12b28..80c9fde257 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveGeoCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveGeoCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveHashCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveHashCommands.java
index 063dd6e285..385b652f2b 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveHashCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveHashCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,11 +19,14 @@
import reactor.core.publisher.Mono;
import java.nio.ByteBuffer;
+import java.time.Duration;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import org.reactivestreams.Publisher;
@@ -36,6 +39,7 @@
import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.data.redis.core.ScanOptions;
+import org.springframework.data.redis.core.types.Expiration;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -44,10 +48,34 @@
*
* @author Christoph Strobl
* @author Mark Paluch
+ * @author Tihomir Mateev
* @since 2.0
*/
public interface ReactiveHashCommands {
+ /**
+ * {@link Command} for hash-bound operations.
+ *
+ * @author Christoph Strobl
+ * @author Tihomir Mateev
+ */
+ class HashFieldsCommand extends KeyCommand {
+
+ private final List fields;
+
+ private HashFieldsCommand(@Nullable ByteBuffer key, List fields) {
+ super(key);
+ this.fields = fields;
+ }
+
+ /**
+ * @return never {@literal null}.
+ */
+ public List getFields() {
+ return fields;
+ }
+ }
+
/**
* {@literal HSET} {@link Command}.
*
@@ -216,15 +244,10 @@ default Mono hMSet(ByteBuffer key, Map fieldVal
* @author Christoph Strobl
* @see Redis Documentation: HGET
*/
- class HGetCommand extends KeyCommand {
-
- private List fields;
+ class HGetCommand extends HashFieldsCommand {
private HGetCommand(@Nullable ByteBuffer key, List fields) {
-
- super(key);
-
- this.fields = fields;
+ super(key, fields);
}
/**
@@ -263,14 +286,7 @@ public HGetCommand from(ByteBuffer key) {
Assert.notNull(key, "Key must not be null");
- return new HGetCommand(key, fields);
- }
-
- /**
- * @return never {@literal null}.
- */
- public List getFields() {
- return fields;
+ return new HGetCommand(key, getFields());
}
}
@@ -289,7 +305,7 @@ default Mono hGet(ByteBuffer key, ByteBuffer field) {
/**
* Get values for given {@literal fields} from hash at {@literal key}. Values are in the order of the requested keys.
- * Absent field values are represented using {@code null} in the resulting {@link List}.
+ * Absent field values are represented using {@literal null} in the resulting {@link List}.
*
* @param key must not be {@literal null}.
* @param fields must not be {@literal null}.
@@ -306,7 +322,7 @@ default Mono> hMGet(ByteBuffer key, Collection fiel
/**
* Get values for given {@literal fields} from hash at {@literal key}. Values are in the order of the requested keys.
- * Absent field values are represented using {@code null} in the resulting {@link List}.
+ * Absent field values are represented using {@literal null} in the resulting {@link List}.
*
* @param commands must not be {@literal null}.
* @return
@@ -394,15 +410,10 @@ default Mono hExists(ByteBuffer key, ByteBuffer field) {
* @author Christoph Strobl
* @see Redis Documentation: HDEL
*/
- class HDelCommand extends KeyCommand {
-
- private final List fields;
+ class HDelCommand extends HashFieldsCommand {
private HDelCommand(@Nullable ByteBuffer key, List fields) {
-
- super(key);
-
- this.fields = fields;
+ super(key, fields);
}
/**
@@ -441,14 +452,7 @@ public HDelCommand from(ByteBuffer key) {
Assert.notNull(key, "Key must not be null");
- return new HDelCommand(key, fields);
- }
-
- /**
- * @return never {@literal null}.
- */
- public List getFields() {
- return fields;
+ return new HDelCommand(key, getFields());
}
}
@@ -842,4 +846,413 @@ default Mono hStrLen(ByteBuffer key, ByteBuffer field) {
* @since 2.1
*/
Flux> hStrLen(Publisher commands);
+
+ /**
+ * @since 3.5
+ */
+ class HashExpireCommand extends HashFieldsCommand {
+
+ private final Expiration expiration;
+ private final ExpirationOptions options;
+
+ private HashExpireCommand(@Nullable ByteBuffer key, List fields, Expiration expiration,
+ ExpirationOptions options) {
+
+ super(key, fields);
+
+ this.expiration = expiration;
+ this.options = options;
+ }
+
+ /**
+ * Creates a new {@link HashExpireCommand}.
+ *
+ * @param fields the {@code field} names to apply expiration to
+ * @param timeout the actual timeout
+ * @param unit the unit of measure for the {@code timeout}.
+ * @return new instance of {@link HashExpireCommand}.
+ */
+ public static HashExpireCommand expire(List fields, long timeout, TimeUnit unit) {
+
+ Assert.notNull(fields, "Field must not be null");
+ return expire(fields, Expiration.from(timeout, unit));
+ }
+
+ /**
+ * Creates a new {@link HashExpireCommand}.
+ *
+ * @param fields the {@code field} names to apply expiration to.
+ * @param ttl the actual timeout.
+ * @return new instance of {@link HashExpireCommand}.
+ */
+ public static HashExpireCommand expire(List fields, Duration ttl) {
+
+ Assert.notNull(fields, "Field must not be null");
+ return expire(fields, Expiration.from(ttl));
+ }
+
+ /**
+ * Creates a new {@link HashExpireCommand}.
+ *
+ * @param fields the {@code field} names to apply expiration to
+ * @param expiration the {@link Expiration} to apply to the given {@literal fields}.
+ * @return new instance of {@link HashExpireCommand}.
+ */
+ public static HashExpireCommand expire(List fields, Expiration expiration) {
+ return new HashExpireCommand(null, fields, expiration, ExpirationOptions.none());
+ }
+
+ /**
+ * Creates a new {@link HashExpireCommand}.
+ *
+ * @param fields the {@code field} names to apply expiration to
+ * @param ttl the unix point in time when to expire the given {@literal fields}.
+ * @param precision can be {@link TimeUnit#SECONDS} or {@link TimeUnit#MILLISECONDS}.
+ * @return new instance of {@link HashExpireCommand}.
+ */
+ public static HashExpireCommand expireAt(List fields, Instant ttl, TimeUnit precision) {
+
+ if (precision.compareTo(TimeUnit.MILLISECONDS) > 0) {
+ return expire(fields, Expiration.unixTimestamp(ttl.getEpochSecond(), TimeUnit.SECONDS));
+ }
+
+ return expire(fields, Expiration.unixTimestamp(ttl.toEpochMilli(), TimeUnit.MILLISECONDS));
+ }
+
+ /**
+ * @param key the {@literal key} from which to expire the {@literal fields} from.
+ * @return new instance of {@link HashExpireCommand}.
+ */
+ public HashExpireCommand from(ByteBuffer key) {
+ return new HashExpireCommand(key, getFields(), expiration, options);
+ }
+
+ /**
+ * @param options additional options to be sent along with the command.
+ * @return new instance of {@link HashExpireCommand}.
+ */
+ public HashExpireCommand withOptions(ExpirationOptions options) {
+ return new HashExpireCommand(getKey(), getFields(), getExpiration(), options);
+ }
+
+ public Expiration getExpiration() {
+ return expiration;
+ }
+
+ public ExpirationOptions getOptions() {
+ return options;
+ }
+ }
+
+ /**
+ * Expire a given {@literal field} after a given {@link Duration} of time, measured in milliseconds, has passed.
+ *
+ * @param key must not be {@literal null}.
+ * @param field must not be {@literal null}.
+ * @param duration must not be {@literal null}.
+ * @return a {@link Mono} emitting the expiration result - {@code 2} indicating the specific field is deleted already
+ * due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time is set/updated;
+ * {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is not met);
+ * {@code -2} indicating there is no such field;
+ * @see Redis Documentation: HEXPIRE
+ * @since 3.5
+ */
+ default Mono hExpire(ByteBuffer key, Duration duration, ByteBuffer field) {
+
+ Assert.notNull(duration, "Duration must not be null");
+
+ return hExpire(key, duration, Collections.singletonList(field)).singleOrEmpty();
+ }
+
+ /**
+ * Expire a {@link List} of {@literal field} after a given {@link Duration} of time, measured in milliseconds, has
+ * passed.
+ *
+ * @param key must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @param duration must not be {@literal null}.
+ * @return a {@link Flux} emitting the expiration results one by one, {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time
+ * is set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition
+ * is not met); {@code -2} indicating there is no such field;
+ * @see Redis Documentation: HEXPIRE
+ * @since 3.5
+ */
+ default Flux hExpire(ByteBuffer key, Duration duration, List fields) {
+
+ Assert.notNull(duration, "Duration must not be null");
+
+ return applyHashFieldExpiration(Flux.just(HashExpireCommand.expire(fields, duration).from(key)))
+ .mapNotNull(NumericResponse::getOutput);
+ }
+
+ /**
+ * Expire a {@link List} of {@literal field} after a given {@link Duration} of time, measured in milliseconds, has
+ * passed.
+ *
+ * @param commands must not be {@literal null}.
+ * @return a {@link Flux} emitting the expiration results one by one, {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time
+ * is set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition
+ * is not met); {@code -2} indicating there is no such field;
+ * @since 3.5
+ * @see Redis Documentation: HEXPIRE
+ */
+ Flux> applyHashFieldExpiration(Publisher commands);
+
+ /**
+ * Expire a given {@literal field} after a given {@link Duration} of time, measured in milliseconds, has passed.
+ *
+ * @param key must not be {@literal null}.
+ * @param field must not be {@literal null}.
+ * @param duration must not be {@literal null}.
+ * @return a {@link Mono} emitting the expiration result - {@code 2} indicating the specific field is deleted already
+ * due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time is set/updated;
+ * {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is not met);
+ * {@code -2} indicating there is no such field;
+ * @see Redis Documentation: HEXPIRE
+ * @since 3.5
+ */
+ default Mono hpExpire(ByteBuffer key, Duration duration, ByteBuffer field) {
+
+ Assert.notNull(duration, "Duration must not be null");
+
+ return hpExpire(key, duration, Collections.singletonList(field)).singleOrEmpty();
+ }
+
+ /**
+ * Expire a {@link List} of {@literal field} after a given {@link Duration} of time, measured in milliseconds, has
+ * passed.
+ *
+ * @param key must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @param duration must not be {@literal null}.
+ * @return a {@link Flux} emitting the expiration results one by one, {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time
+ * is set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition
+ * is not met); {@code -2} indicating there is no such field;
+ * @see Redis Documentation: HEXPIRE
+ * @since 3.5
+ */
+ default Flux hpExpire(ByteBuffer key, Duration duration, List fields) {
+
+ Assert.notNull(duration, "Duration must not be null");
+
+ return applyHashFieldExpiration(Flux.just(new HashExpireCommand(key, fields,
+ Expiration.from(duration.toMillis(), TimeUnit.MILLISECONDS), ExpirationOptions.none())))
+ .mapNotNull(NumericResponse::getOutput);
+ }
+
+ /**
+ * Expire a given {@literal field} in a given {@link Instant} of time, indicated as an absolute
+ * Unix timestamp in seconds since Unix epoch
+ *
+ * @param key must not be {@literal null}.
+ * @param field must not be {@literal null}.
+ * @param expireAt must not be {@literal null}.
+ * @return a {@link Mono} emitting the expiration result - {@code 2} indicating the specific field is deleted already
+ * due to expiration, or provided expiry interval is in the past; {@code 1} indicating expiration time is
+ * set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is
+ * not met); {@code -2} indicating there is no such field;
+ * @see Redis Documentation: HEXPIREAT
+ * @since 3.5
+ */
+ default Mono hExpireAt(ByteBuffer key, Instant expireAt, ByteBuffer field) {
+
+ Assert.notNull(expireAt, "Duration must not be null");
+
+ return hExpireAt(key, expireAt, Collections.singletonList(field)).singleOrEmpty();
+ }
+
+ /**
+ * Expire a {@link List} of {@literal field} in a given {@link Instant} of time, indicated as an absolute
+ * Unix timestamp in seconds since Unix epoch
+ *
+ * @param key must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @param expireAt must not be {@literal null}.
+ * @return a {@link Flux} emitting the expiration results one by one, {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is in the past; {@code 1} indicating
+ * expiration time is set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX |
+ * GT | LT condition is not met); {@code -2} indicating there is no such field;
+ * @see Redis Documentation: HEXPIREAT
+ * @since 3.5
+ */
+ default Flux hExpireAt(ByteBuffer key, Instant expireAt, List fields) {
+
+ Assert.notNull(expireAt, "Duration must not be null");
+
+ return applyHashFieldExpiration(Flux.just(HashExpireCommand.expireAt(fields, expireAt, TimeUnit.SECONDS).from(key)))
+ .mapNotNull(NumericResponse::getOutput);
+ }
+
+ /**
+ * Expire a given {@literal field} in a given {@link Instant} of time, indicated as an absolute
+ * Unix timestamp in milliseconds since Unix epoch
+ *
+ * @param key must not be {@literal null}.
+ * @param field must not be {@literal null}.
+ * @param expireAt must not be {@literal null}.
+ * @return a {@link Mono} emitting the expiration result - {@code 2} indicating the specific field is deleted already
+ * due to expiration, or provided expiry interval is in the past; {@code 1} indicating expiration time is
+ * set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is
+ * not met); {@code -2} indicating there is no such field;
+ * @see Redis Documentation: HPEXPIREAT
+ * @since 3.5
+ */
+ default Mono hpExpireAt(ByteBuffer key, Instant expireAt, ByteBuffer field) {
+
+ Assert.notNull(expireAt, "Duration must not be null");
+
+ return hpExpireAt(key, expireAt, Collections.singletonList(field)).singleOrEmpty();
+ }
+
+ /**
+ * Expire a {@link List} of {@literal field} in a given {@link Instant} of time, indicated as an absolute
+ * Unix timestamp in milliseconds since Unix epoch
+ *
+ * @param key must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @param expireAt must not be {@literal null}.
+ * @return a {@link Flux} emitting the expiration results one by one, {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is in the past; {@code 1} indicating
+ * expiration time is set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX |
+ * GT | LT condition is not met); {@code -2} indicating there is no such field;
+ * @see Redis Documentation: HPEXPIREAT
+ * @since 3.5
+ */
+ default Flux hpExpireAt(ByteBuffer key, Instant expireAt, List fields) {
+
+ Assert.notNull(expireAt, "Duration must not be null");
+
+ return applyHashFieldExpiration(
+ Flux.just(HashExpireCommand.expireAt(fields, expireAt, TimeUnit.MILLISECONDS).from(key)))
+ .mapNotNull(NumericResponse::getOutput);
+ }
+
+ /**
+ * Persist a given {@literal field} removing any associated expiration, measured as absolute
+ * Unix timestamp in seconds since Unix epoch
+ *
+ * @param key must not be {@literal null}.
+ * @param field must not be {@literal null}.
+ * @return a {@link Mono} emitting the persist result - {@code 1} indicating expiration time is removed; {@code -1}
+ * field has no expiration time to be removed; {@code -2} indicating there is no such field;
+ * @see Redis Documentation: HPERSIST
+ * @since 3.5
+ */
+ default Mono hPersist(ByteBuffer key, ByteBuffer field) {
+ return hPersist(key, Collections.singletonList(field)).singleOrEmpty();
+ }
+
+ /**
+ * Persist a given {@link List} of {@literal field} removing any associated expiration.
+ *
+ * @param key must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a {@link Flux} emitting the persisting results one by one - {@code 1} indicating expiration time is
+ * removed; {@code -1} field has no expiration time to be removed; {@code -2} indicating there is no such
+ * field;
+ * @see Redis Documentation: HPERSIST
+ * @since 3.5
+ */
+ default Flux hPersist(ByteBuffer key, List fields) {
+ return hPersist(Flux.just(new HashFieldsCommand(key, fields))).mapNotNull(NumericResponse::getOutput);
+ }
+
+ /**
+ * Persist a given {@link List} of {@literal field} removing any associated expiration.
+ *
+ * @param commands must not be {@literal null}.
+ * @return a {@link Flux} emitting the persisting results one by one - {@code 1} indicating expiration time is
+ * removed; {@code -1} field has no expiration time to be removed; {@code -2} indicating there is no such
+ * field; * @since 3.5
+ * @see Redis Documentation: HPERSIST
+ */
+ Flux> hPersist(Publisher commands);
+
+ /**
+ * Returns the time-to-live of a given {@literal field} in seconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param field must not be {@literal null}.
+ * @return a {@link Mono} emitting the TTL result - the time to live in seconds; or a negative value to signal an
+ * error. The command returns {@code -1} if the key exists but has no associated expiration time. The command
+ * returns {@code -2} if the key does not exist;
+ * @see Redis Documentation: HTTL
+ * @since 3.5
+ */
+ default Mono hTtl(ByteBuffer key, ByteBuffer field) {
+ return hTtl(key, Collections.singletonList(field)).singleOrEmpty();
+ }
+
+ /**
+ * Returns the time-to-live of all the given {@literal field} in the {@link List} in seconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a {@link Flux} emitting the TTL results one by one - the time to live in seconds; or a negative value to
+ * signal an error. The command returns {@code -1} if the key exists but has no associated expiration time.
+ * The command returns {@code -2} if the key does not exist;
+ * @see Redis Documentation: HTTL
+ * @since 3.5
+ */
+ default Flux hTtl(ByteBuffer key, List fields) {
+ return hTtl(Flux.just(new HashFieldsCommand(key, fields))).mapNotNull(NumericResponse::getOutput);
+ }
+
+ /**
+ * Returns the time-to-live of all the given {@literal field} in the {@link List} in seconds.
+ *
+ * @param commands must not be {@literal null}.
+ * @return a {@link Flux} emitting the persisting results one by one - the time to live in seconds; or a negative
+ * value to signal an error. The command returns {@code -1} if the key exists but has no associated expiration
+ * time. The command returns {@code -2} if the key does not exist;
+ * @since 3.5
+ * @see Redis Documentation: HTTL
+ */
+ Flux> hTtl(Publisher commands);
+
+ /**
+ * Returns the time-to-live of a given {@literal field} in milliseconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param field must not be {@literal null}.
+ * @return a {@link Mono} emitting the TTL result - the time to live in milliseconds; or a negative value to signal an
+ * error. The command returns {@code -1} if the key exists but has no associated expiration time. The command
+ * returns {@code -2} if the key does not exist;
+ * @see Redis Documentation: HPTTL
+ * @since 3.5
+ */
+ default Mono hpTtl(ByteBuffer key, ByteBuffer field) {
+ return hpTtl(key, Collections.singletonList(field)).singleOrEmpty();
+ }
+
+ /**
+ * Returns the time-to-live of all the given {@literal field} in the {@link List} in milliseconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a {@link Flux} emitting the TTL results one by one - the time to live in milliseconds; or a negative value
+ * to signal an error. The command returns {@code -1} if the key exists but has no associated expiration time.
+ * The command returns {@code -2} if the key does not exist;
+ * @see Redis Documentation: HPTTL
+ * @since 3.5
+ */
+ default Flux hpTtl(ByteBuffer key, List fields) {
+ return hpTtl(Flux.just(new HashFieldsCommand(key, fields))).mapNotNull(NumericResponse::getOutput);
+ }
+
+ /**
+ * Returns the time-to-live of all the given {@literal field} in the {@link List} in milliseconds.
+ *
+ * @param commands must not be {@literal null}.
+ * @return a {@link Flux} emitting the persisting results one by one - the time to live in milliseconds; or a negative
+ * value to signal an error. The command returns {@code -1} if the key exists but has no associated expiration
+ * time. The command returns {@code -2} if the key does not exist;
+ * @since 3.5
+ * @see Redis Documentation: HPTTL
+ */
+ Flux> hpTtl(Publisher commands);
+
}
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveHyperLogLogCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveHyperLogLogCommands.java
index d10975851a..cba1cc8fac 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveHyperLogLogCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveHyperLogLogCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveKeyCommands.java
index bd3f4d73dd..3354cf9af1 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveKeyCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveKeyCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
import java.util.List;
import org.reactivestreams.Publisher;
+
import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
@@ -32,6 +33,7 @@
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.data.redis.core.KeyScanOptions;
import org.springframework.data.redis.core.ScanOptions;
+import org.springframework.data.redis.core.types.Expiration;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -40,6 +42,7 @@
*
* @author Christoph Strobl
* @author Mark Paluch
+ * @author Dahye Anne Lee
* @since 2.0
*/
public interface ReactiveKeyCommands {
@@ -176,6 +179,16 @@ default Mono exists(ByteBuffer key) {
return exists(Mono.just(new KeyCommand(key))).next().map(BooleanResponse::getOutput);
}
+ /**
+ * Determine the number of given {@literal keys} that exist.
+ *
+ * @param keys must not be {@literal null} or {@literal empty}.
+ * @return {@link Mono} emitting {@literal the number of existing keys}.
+ * @see Redis Documentation: EXISTS
+ * @since 3.5
+ */
+ Mono exists(List keys);
+
/**
* Determine if given {@literal key} exists.
*
@@ -513,13 +526,35 @@ default Mono mUnlink(List keys) {
*/
class ExpireCommand extends KeyCommand {
- private @Nullable Duration timeout;
+ private final Expiration expiration;
+ private final ExpirationOptions options;
+
+ private ExpireCommand(ByteBuffer key, Duration timeout) {
+ this(key, Expiration.from(timeout), ExpirationOptions.none());
+ }
- private ExpireCommand(ByteBuffer key, @Nullable Duration timeout) {
+ private ExpireCommand(@Nullable ByteBuffer key, Expiration expiration, ExpirationOptions options) {
super(key);
- this.timeout = timeout;
+ this.expiration = expiration;
+ this.options = options;
+ }
+
+ /**
+ * Creates a new {@link ExpireCommand} given a {@link ByteBuffer key} and {@link Expiration}.
+ *
+ * @param key must not be {@literal null}.
+ * @param expiration must not be {@literal null}.
+ * @return a new {@link ExpireCommand} for {@link ByteBuffer key} and {@link Expiration}.
+ * @since 3.5
+ */
+ public static ExpireCommand expire(ByteBuffer key, Expiration expiration) {
+
+ Assert.notNull(key, "Key must not be null");
+ Assert.notNull(expiration, "Expiration must not be null");
+
+ return new ExpireCommand(key, expiration, ExpirationOptions.none());
}
/**
@@ -532,7 +567,7 @@ public static ExpireCommand key(ByteBuffer key) {
Assert.notNull(key, "Key must not be null");
- return new ExpireCommand(key, null);
+ return new ExpireCommand(key, Expiration.persistent(), ExpirationOptions.none());
}
/**
@@ -545,7 +580,21 @@ public ExpireCommand timeout(Duration timeout) {
Assert.notNull(timeout, "Timeout must not be null");
- return new ExpireCommand(getKey(), timeout);
+ return new ExpireCommand(getKey(), Expiration.from(timeout), options);
+ }
+
+ /**
+ * Applies the {@literal timeout}. Constructs a new command instance with all previously configured properties.
+ *
+ * @param timeout must not be {@literal null}.
+ * @return a new {@link ExpireCommand} with {@literal timeout} applied.
+ * @since 3.5
+ */
+ public ExpireCommand expire(Duration timeout) {
+
+ Assert.notNull(timeout, "Timeout must not be null");
+
+ return new ExpireCommand(getKey(), Expiration.from(timeout), options);
}
/**
@@ -553,10 +602,50 @@ public ExpireCommand timeout(Duration timeout) {
*/
@Nullable
public Duration getTimeout() {
- return timeout;
+
+ if (expiration.isUnixTimestamp() || expiration.isPersistent()) {
+ return null;
+ }
+
+ return Duration.ofMillis(expiration.getExpirationTimeInMilliseconds());
}
+
+ /**
+ * @param options additional options to be sent along with the command.
+ * @return new instance of {@link ExpireCommand}.
+ * @since 3.5
+ */
+ public ExpireCommand withOptions(ExpirationOptions options) {
+ return new ExpireCommand(getKey(), getExpiration(), options);
+ }
+
+ public Expiration getExpiration() {
+ return expiration;
+ }
+
+ public ExpirationOptions getOptions() {
+ return options;
+ }
+
}
+ /**
+ * Expire a {@link List} of {@literal field} after a given {@link Duration} of time, measured in milliseconds, has
+ * passed.
+ *
+ * @param commands must not be {@literal null}.
+ * @return a {@link Flux} emitting the expiration results one by one, {@literal true} if the timeout was set or
+ * {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
+ * skipped because of the provided arguments.
+ * @since 3.5
+ * @see Redis Documentation: EXPIRE
+ * @see Redis Documentation: PEXPIRE
+ * @see Redis Documentation: EXPIREAT
+ * @see Redis Documentation: PEXPIREAT
+ * @see Redis Documentation: PERSIST
+ */
+ Flux> applyExpiration(Publisher commands);
+
/**
* Set time to live for given {@code key} in seconds.
*
@@ -581,7 +670,9 @@ default Mono expire(ByteBuffer key, Duration timeout) {
* result.
* @see Redis Documentation: EXPIRE
*/
- Flux> expire(Publisher commands);
+ default Flux> expire(Publisher commands) {
+ return applyExpiration(commands);
+ }
/**
* Set time to live for given {@code key} in milliseconds.
@@ -607,7 +698,9 @@ default Mono pExpire(ByteBuffer key, Duration timeout) {
* result.
* @see Redis Documentation: PEXPIRE
*/
- Flux> pExpire(Publisher commands);
+ default Flux> pExpire(Publisher commands) {
+ return applyExpiration(commands);
+ }
/**
* {@code EXPIREAT}/{@code PEXPIREAT} command parameters.
@@ -619,12 +712,18 @@ default Mono pExpire(ByteBuffer key, Duration timeout) {
class ExpireAtCommand extends KeyCommand {
private @Nullable Instant expireAt;
+ private final ExpirationOptions options;
+
+ private ExpireAtCommand(ByteBuffer key, Instant expireAt) {
+ this(key, expireAt, ExpirationOptions.none());
+ }
- private ExpireAtCommand(ByteBuffer key, @Nullable Instant expireAt) {
+ private ExpireAtCommand(@Nullable ByteBuffer key, Instant expireAt, ExpirationOptions options) {
super(key);
this.expireAt = expireAt;
+ this.options = options;
}
/**
@@ -653,6 +752,15 @@ public ExpireAtCommand timeout(Instant expireAt) {
return new ExpireAtCommand(getKey(), expireAt);
}
+ /**
+ * @param options additional options to be sent along with the command.
+ * @return new instance of {@link ExpireAtCommand}.
+ * @since 3.5
+ */
+ public ExpireAtCommand withOptions(ExpirationOptions options) {
+ return new ExpireAtCommand(getKey(), getExpireAt(), options);
+ }
+
/**
* @return can be {@literal null}.
*/
@@ -660,6 +768,11 @@ public ExpireAtCommand timeout(Instant expireAt) {
public Instant getExpireAt() {
return expireAt;
}
+
+ public ExpirationOptions getOptions() {
+ return options;
+ }
+
}
/**
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveListCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveListCommands.java
index f3fbd20f3e..d92a99a270 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveListCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveListCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveNumberCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveNumberCommands.java
index bbade90af8..31f80cc460 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveNumberCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveNumberCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactivePubSubCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactivePubSubCommands.java
index d07155b4ab..5ea9d74885 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactivePubSubCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactivePubSubCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-2024 the original author or authors.
+ * Copyright 2018-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveRedisClusterConnection.java b/src/main/java/org/springframework/data/redis/connection/ReactiveRedisClusterConnection.java
index 2dc4efd081..cec6b7991b 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveRedisClusterConnection.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveRedisClusterConnection.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/ReactiveRedisConnection.java
index 7da6929ad3..5dfb6d9db8 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveRedisConnection.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveRedisConnection.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveRedisConnectionFactory.java b/src/main/java/org/springframework/data/redis/connection/ReactiveRedisConnectionFactory.java
index 05e726e753..3492913549 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveRedisConnectionFactory.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveRedisConnectionFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2024 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveScriptingCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveScriptingCommands.java
index 5337763728..4ad5d59004 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveScriptingCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveScriptingCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2024 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveServerCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveServerCommands.java
index d95b718503..c0d65c1a81 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveServerCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveServerCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2024 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveSetCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveSetCommands.java
index 845301dba9..76911fd441 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveSetCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveSetCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveStreamCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveStreamCommands.java
index 460a8cc3fe..2860dc691c 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveStreamCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveStreamCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-2024 the original author or authors.
+ * Copyright 2018-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.data.redis.connection.RedisStreamCommands.XClaimOptions;
+import org.springframework.data.redis.connection.RedisStreamCommands.XAddOptions;
import org.springframework.data.redis.connection.RedisStreamCommands.XPendingOptions;
import org.springframework.data.redis.connection.stream.ByteBufferRecord;
import org.springframework.data.redis.connection.stream.Consumer;
@@ -58,6 +59,7 @@
* @author Tugdual Grall
* @author Dengliming
* @author Mark John Moreno
+ * @author jinkshower
* @since 2.2
*/
public interface ReactiveStreamCommands {
@@ -335,7 +337,7 @@ public Long getMaxlen() {
* @since 2.3
*/
public boolean hasMaxlen() {
- return maxlen != null && maxlen > 0;
+ return maxlen != null;
}
/**
@@ -394,11 +396,40 @@ default Mono xAdd(ByteBufferRecord record) {
return xAdd(Mono.just(AddStreamRecord.of(record))).next().map(CommandResponse::getOutput);
}
+ /**
+ * Add stream record with the specified options.
+ *
+ * @param record must not be {@literal null}.
+ * @param xAddOptions parameters for the {@literal XADD} call. Must not be {@literal null}.
+ * @return {@link Mono} the {@link RecordId id}.
+ * @see Redis Documentation: XADD
+ * @since 3.4
+ */
+ default Mono xAdd(ByteBufferRecord record, XAddOptions xAddOptions) {
+
+ Assert.notNull(record, "Record must not be null");
+ Assert.notNull(xAddOptions, "XAddOptions must not be null");
+
+ AddStreamRecord addStreamRecord = AddStreamRecord.of(record)
+ .approximateTrimming(xAddOptions.isApproximateTrimming())
+ .makeNoStream(xAddOptions.isNoMkStream());
+
+ if (xAddOptions.hasMaxlen()) {
+ addStreamRecord = addStreamRecord.maxlen(xAddOptions.getMaxlen());
+ }
+
+ if (xAddOptions.hasMinId()) {
+ addStreamRecord = addStreamRecord.minId(xAddOptions.getMinId());
+ }
+
+ return xAdd(Mono.just(addStreamRecord)).next().map(CommandResponse::getOutput);
+ }
+
/**
* Add stream record with given {@literal body} to {@literal key}.
*
* @param commands must not be {@literal null}.
- * @return {@link Flux} emitting the {@link RecordId} on by for for the given {@link AddStreamRecord} commands.
+ * @return {@link Flux} emitting the {@link RecordId} on by for the given {@link AddStreamRecord} commands.
* @see Redis Documentation: XADD
*/
Flux> xAdd(Publisher commands);
@@ -654,7 +685,7 @@ default Mono xPending(ByteBuffer key, String groupName)
Assert.notNull(key, "Key must not be null");
Assert.notNull(groupName, "GroupName must not be null");
- return xPendingSummary(Mono.just(new PendingRecordsCommand(key, groupName, null, Range.unbounded(), null))).next()
+ return xPendingSummary(Mono.just(PendingRecordsCommand.pending(key, groupName))).next()
.map(CommandResponse::getOutput);
}
@@ -695,7 +726,7 @@ default Mono xPending(ByteBuffer key, Consumer consumer) {
*/
@Nullable
default Mono xPending(ByteBuffer key, String groupName, String consumerName) {
- return xPending(Mono.just(new PendingRecordsCommand(key, groupName, consumerName, Range.unbounded(), null))).next()
+ return xPending(Mono.just(PendingRecordsCommand.pending(key, groupName).consumer(consumerName))).next()
.map(CommandResponse::getOutput);
}
@@ -712,7 +743,7 @@ default Mono xPending(ByteBuffer key, String groupName, String
* @since 2.3
*/
default Mono xPending(ByteBuffer key, String groupName, Range> range, Long count) {
- return xPending(Mono.just(new PendingRecordsCommand(key, groupName, null, range, count))).next()
+ return xPending(Mono.just(PendingRecordsCommand.pending(key, groupName).range(range, count))).next()
.map(CommandResponse::getOutput);
}
@@ -748,8 +779,8 @@ default Mono xPending(ByteBuffer key, Consumer consumer, Range<
*/
default Mono xPending(ByteBuffer key, String groupName, String consumerName, Range> range,
Long count) {
- return xPending(Mono.just(new PendingRecordsCommand(key, groupName, consumerName, range, count))).next()
- .map(CommandResponse::getOutput);
+ return xPending(Mono.just(PendingRecordsCommand.pending(key, groupName).consumer(consumerName).range(range, count)))
+ .next().map(CommandResponse::getOutput);
}
/**
@@ -801,9 +832,15 @@ static PendingRecordsCommand pending(ByteBuffer key, String groupName) {
/**
* Create new {@link PendingRecordsCommand} with given {@link Range} and limit.
*
+ * @param range must not be {@literal null}.
+ * @param count the max number of messages to return. Must not be negative.
* @return new instance of {@link XPendingOptions}.
*/
- public PendingRecordsCommand range(Range range, Long count) {
+ public PendingRecordsCommand range(Range> range, Long count) {
+
+ Assert.notNull(range, "Range must not be null");
+ Assert.isTrue(count > -1, "Count must not be negative");
+
return new PendingRecordsCommand(getKey(), groupName, consumerName, range, count);
}
@@ -855,7 +892,7 @@ public boolean hasConsumer() {
* @return {@literal true} count is set.
*/
public boolean isLimited() {
- return count != null && count > -1;
+ return count != null;
}
}
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveStringCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveStringCommands.java
index 493b055dae..3c1bfc8eea 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveStringCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveStringCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,6 +47,7 @@
*
* @author Christoph Strobl
* @author Mark Paluch
+ * @author Marcin Grzejszczak
* @since 2.0
*/
public interface ReactiveStringCommands {
@@ -193,6 +194,41 @@ default Mono set(ByteBuffer key, ByteBuffer value, Expiration expiratio
*/
Flux> set(Publisher commands);
+ /**
+ * Set {@literal value} for {@literal key} with {@literal expiration} and {@literal options}. Return the old string
+ * stored at key, or empty if key did not exist. An error is returned and SET aborted if the value stored at key is
+ * not a string.
+ *
+ * @param key must not be {@literal null}.
+ * @param value must not be {@literal null}.
+ * @param expiration must not be {@literal null}. Use {@link Expiration#persistent()} for no expiration time or
+ * {@link Expiration#keepTtl()} to keep the existing.
+ * @param option must not be {@literal null}.
+ * @return
+ * @see Redis Documentation: SET
+ * @since 3.5
+ */
+ @Nullable
+ default Mono setGet(ByteBuffer key, ByteBuffer value, Expiration expiration, SetOption option) {
+
+ Assert.notNull(key, "Key must not be null");
+ Assert.notNull(value, "Value must not be null");
+
+ return setGet(Mono.just(SetCommand.set(key).value(value).withSetOption(option).expiring(expiration))).next()
+ .map(CommandResponse::getOutput);
+ }
+
+ /**
+ * Set each and every item separately by invoking {@link SetCommand}. Return the old string stored at key, or empty if
+ * key did not exist. An error is returned and SET aborted if the value stored at key is not a string.
+ *
+ * @param commands must not be {@literal null}.
+ * @return {@link Flux} of {@link ByteBufferResponse} holding the {@link SetCommand} along with the command result.
+ * @see Redis Documentation: SET
+ * @since 3.5
+ */
+ Flux> setGet(Publisher commands);
+
/**
* Get single element stored at {@literal key}.
*
@@ -350,7 +386,7 @@ default Mono getSet(ByteBuffer key, ByteBuffer value) {
/**
* Get multiple values in one batch. Values are in the order of the requested keys. Absent field values are
- * represented using {@code null} in the resulting {@link List}.
+ * represented using {@literal null} in the resulting {@link List}.
*
* @param keys must not be {@literal null}.
* @return
@@ -365,7 +401,7 @@ default Mono> mGet(List keys) {
/**
* Get multiple values at for {@literal keysets} in batches. Values are in the order of the requested keys. Absent
- * field values are represented using {@code null} in the resulting {@link List}.
+ * field values are represented using {@literal null} in the resulting {@link List}.
*
* @param keysets must not be {@literal null}.
* @return
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveSubscription.java b/src/main/java/org/springframework/data/redis/connection/ReactiveSubscription.java
index 2929544c97..153d139e8f 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveSubscription.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveSubscription.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-2024 the original author or authors.
+ * Copyright 2018-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java
index 2abbb212ce..c4037e2c55 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1974,7 +1974,7 @@ default Mono zCard(ByteBuffer key) {
}
/**
- * Get the size of sorted set with {@linByteBuffer keyCommand#getKey()}.
+ * Get the size of sorted set with {@link ByteBuffer keyCommand#getKey()}.
*
* @param commands must not be {@literal null}.
* @return
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisClusterCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisClusterCommands.java
index 9a47dd7413..018a78b6cb 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisClusterCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisClusterCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2024 the original author or authors.
+ * Copyright 2015-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisClusterCommandsProvider.java b/src/main/java/org/springframework/data/redis/connection/RedisClusterCommandsProvider.java
index 7427559004..e26842e779 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisClusterCommandsProvider.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisClusterCommandsProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022-2024 the original author or authors.
+ * Copyright 2022-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisClusterConfiguration.java b/src/main/java/org/springframework/data/redis/connection/RedisClusterConfiguration.java
index 620e132d02..8960d32ae1 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisClusterConfiguration.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisClusterConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2024 the original author or authors.
+ * Copyright 2015-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,6 @@
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.data.redis.connection.RedisConfiguration.ClusterConfiguration;
-import org.springframework.data.redis.util.RedisAssertions;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.NumberUtils;
@@ -63,11 +62,9 @@ public RedisClusterConfiguration() {}
/**
* Creates a new {@link RedisClusterConfiguration} for given {@link String hostPort} combinations.
*
- *
- *
+ *
* clusterHostAndPorts[0] = 127.0.0.1:23679
* clusterHostAndPorts[1] = 127.0.0.1:23680 ...
- *
*
*
* @param clusterNodes must not be {@literal null}.
@@ -161,7 +158,10 @@ public Set getClusterNodes() {
* @param node must not be {@literal null}.
*/
public void addClusterNode(RedisNode node) {
- this.clusterNodes.add(RedisAssertions.requireNonNull(node, "ClusterNode must not be null"));
+
+ Assert.notNull(node, "ClusterNode must not be null");
+
+ this.clusterNodes.add(node);
}
/**
@@ -211,7 +211,10 @@ public String getUsername() {
@Override
public void setPassword(RedisPassword password) {
- this.password = RedisAssertions.requireNonNull(password, "RedisPassword must not be null");
+
+ Assert.notNull(password, "RedisPassword must not be null");
+
+ this.password = password;
}
@Override
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisClusterConnection.java b/src/main/java/org/springframework/data/redis/connection/RedisClusterConnection.java
index d079fae2f3..585780ecb1 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisClusterConnection.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisClusterConnection.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2024 the original author or authors.
+ * Copyright 2015-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisClusterNode.java b/src/main/java/org/springframework/data/redis/connection/RedisClusterNode.java
index 7f84a2110d..7a6443d484 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisClusterNode.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisClusterNode.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2024 the original author or authors.
+ * Copyright 2015-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@
import java.util.LinkedHashSet;
import java.util.Set;
-import org.springframework.data.redis.util.RedisAssertions;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@@ -76,7 +75,9 @@ public RedisClusterNode(String id) {
this(SlotRange.empty());
- this.id = RedisAssertions.requireNonNull(id, "Id must not be null");
+ Assert.notNull(id, "Id must not be null");
+
+ this.id = id;
}
/**
@@ -86,8 +87,10 @@ public RedisClusterNode(String id) {
*/
public RedisClusterNode(SlotRange slotRange) {
+ Assert.notNull(slotRange, "SlotRange must not be null");
+
this.flags = Collections.emptySet();
- this.slotRange = RedisAssertions.requireNonNull(slotRange,"SlotRange must not be null");
+ this.slotRange = slotRange;
}
/**
@@ -101,8 +104,10 @@ public RedisClusterNode(String host, int port, SlotRange slotRange) {
super(host, port);
+ Assert.notNull(slotRange, "SlotRange must not be null");
+
this.flags = Collections.emptySet();
- this.slotRange = RedisAssertions.requireNonNull(slotRange,"SlotRange must not be null");
+ this.slotRange = slotRange;
}
/**
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisClusterServerCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisClusterServerCommands.java
index 0802c5a7ac..1bcac4c1f7 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisClusterServerCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisClusterServerCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2024 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisCommands.java
index 2b60126aad..1ebb48b83f 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisCommandsProvider.java b/src/main/java/org/springframework/data/redis/connection/RedisCommandsProvider.java
index 0a1c7a18ed..24cfc387f9 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisCommandsProvider.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisCommandsProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022-2024 the original author or authors.
+ * Copyright 2022-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisConfiguration.java b/src/main/java/org/springframework/data/redis/connection/RedisConfiguration.java
index 395ccfc85f..053ce917de 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisConfiguration.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-2024 the original author or authors.
+ * Copyright 2018-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisConnection.java b/src/main/java/org/springframework/data/redis/connection/RedisConnection.java
index b3021e64ef..69917d5391 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisConnection.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisConnection.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisConnectionCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisConnectionCommands.java
index 704da97a77..74875a4bb2 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisConnectionCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisConnectionCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisConnectionFactory.java b/src/main/java/org/springframework/data/redis/connection/RedisConnectionFactory.java
index 88f74d8a56..d694630701 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisConnectionFactory.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisConnectionFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisGeoCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisGeoCommands.java
index 815363f36c..fce11eb2f6 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisGeoCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisGeoCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2024 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisHashCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisHashCommands.java
index 8642fc837e..cd99e4a516 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisHashCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisHashCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,13 +15,16 @@
*/
package org.springframework.data.redis.connection;
+import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.lang.Nullable;
+import org.springframework.util.ObjectUtils;
/**
* Hash-specific commands supported by Redis.
@@ -29,6 +32,7 @@
* @author Costin Leau
* @author Christoph Strobl
* @author Mark Paluch
+ * @author Tihomir Mateev
*/
public interface RedisHashCommands {
@@ -69,7 +73,7 @@ public interface RedisHashCommands {
/**
* Get values for given {@code fields} from hash at {@code key}. Values are in the order of the requested keys Absent
- * field values are represented using {@code null} in the resulting {@link List}.
+ * field values are represented using {@literal null} in the resulting {@link List}.
*
* @param key must not be {@literal null}.
* @param fields must not be {@literal empty}.
@@ -249,4 +253,314 @@ public interface RedisHashCommands {
*/
@Nullable
Long hStrLen(byte[] key, byte[] field);
+
+ /**
+ * Apply a given {@link org.springframework.data.redis.core.types.Expiration} to the given {@literal fields}.
+ *
+ * @param key must not be {@literal null}.
+ * @param expiration the {@link org.springframework.data.redis.core.types.Expiration} to apply.
+ * @param fields the names of the {@literal fields} to apply the {@literal expiration} to.
+ * @return a {@link List} holding the command result for each field in order - {@code 2} indicating the specific field
+ * is deleted already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration
+ * time is set/updated; {@code 0} indicating the expiration time is not set; {@code -2} indicating there is no
+ * such field;
+ * @since 3.5
+ */
+ default @Nullable List applyHashFieldExpiration(byte[] key,
+ org.springframework.data.redis.core.types.Expiration expiration, byte[]... fields) {
+ return applyHashFieldExpiration(key, expiration, ExpirationOptions.none(), fields);
+ }
+
+ /**
+ * @param key must not be {@literal null}.
+ * @param expiration the {@link org.springframework.data.redis.core.types.Expiration} to apply.
+ * @param options additional options to be sent along with the command.
+ * @param fields the names of the {@literal fields} to apply the {@literal expiration} to.
+ * @return a {@link List} holding the command result for each field in order - {@code 2} indicating the specific field
+ * is deleted already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration
+ * time is set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT
+ * condition is not met); {@code -2} indicating there is no such field;
+ * @since 3.5
+ */
+ @Nullable
+ default List applyHashFieldExpiration(byte[] key,
+ org.springframework.data.redis.core.types.Expiration expiration, ExpirationOptions options, byte[]... fields) {
+
+ if (expiration.isPersistent()) {
+ return hPersist(key, fields);
+ }
+
+ if (ObjectUtils.nullSafeEquals(ExpirationOptions.none(), options)) {
+ if (ObjectUtils.nullSafeEquals(TimeUnit.MILLISECONDS, expiration.getTimeUnit())) {
+ if (expiration.isUnixTimestamp()) {
+ return hpExpireAt(key, expiration.getExpirationTimeInMilliseconds(), fields);
+ }
+ return hpExpire(key, expiration.getExpirationTimeInMilliseconds(), fields);
+ }
+ if (expiration.isUnixTimestamp()) {
+ return hExpireAt(key, expiration.getExpirationTimeInSeconds(), fields);
+ }
+ return hExpire(key, expiration.getExpirationTimeInSeconds(), fields);
+ }
+
+ if (ObjectUtils.nullSafeEquals(TimeUnit.MILLISECONDS, expiration.getTimeUnit())) {
+ if (expiration.isUnixTimestamp()) {
+ return hpExpireAt(key, expiration.getExpirationTimeInMilliseconds(), options.getCondition(), fields);
+ }
+
+ return hpExpire(key, expiration.getExpirationTimeInMilliseconds(), options.getCondition(), fields);
+ }
+
+ if (expiration.isUnixTimestamp()) {
+ return hExpireAt(key, expiration.getExpirationTimeInSeconds(), options.getCondition(), fields);
+ }
+
+ return hExpire(key, expiration.getExpirationTimeInSeconds(), options.getCondition(), fields);
+ }
+
+ /**
+ * Set time to live for given {@code fields} in seconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param seconds the amount of time after which the fields will be expired in seconds, must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time
+ * is set/updated; {@code 0} indicating the expiration time is not set; {@code -2} indicating there is no such
+ * field; {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: HEXPIRE
+ * @since 3.5
+ */
+ @Nullable
+ default List hExpire(byte[] key, long seconds, byte[]... fields) {
+ return hExpire(key, seconds, ExpirationOptions.Condition.ALWAYS, fields);
+ }
+
+ /**
+ * Set time to live for given {@code fields}.
+ *
+ * @param key must not be {@literal null}.
+ * @param ttl the amount of time after which the fields will be expired in {@link Duration#toSeconds() seconds}
+ * precision, must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time
+ * is set/updated; {@code 0} indicating the expiration time is not set; {@code -2} indicating there is no such
+ * field; {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: HEXPIRE
+ * @since 3.5
+ */
+ @Nullable
+ default List hExpire(byte[] key, Duration ttl, byte[]... fields) {
+ return hExpire(key, ttl.toSeconds(), fields);
+ }
+
+ /**
+ * Set time to live for given {@code fields} in seconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param seconds the amount of time after which the fields will be expired in seconds, must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @param condition the condition for expiration, must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time
+ * is set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition
+ * is not met); {@code -2} indicating there is no such field; {@literal null} when used in pipeline /
+ * transaction.
+ * @see Redis Documentation: HEXPIRE
+ * @since 3.5
+ */
+ @Nullable
+ List hExpire(byte[] key, long seconds, ExpirationOptions.Condition condition, byte[]... fields);
+
+ /**
+ * Set time to live for given {@code fields} in milliseconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param millis the amount of time after which the fields will be expired in milliseconds, must not be
+ * {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time
+ * is set/updated; {@code 0} indicating the expiration time is not set ; {@code -2} indicating there is no
+ * such field; {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: HPEXPIRE
+ * @since 3.5
+ */
+ @Nullable
+ default List hpExpire(byte[] key, long millis, byte[]... fields) {
+ return hpExpire(key, millis, ExpirationOptions.Condition.ALWAYS, fields);
+ }
+
+ /**
+ * Set time to live for given {@code fields} in milliseconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param ttl the amount of time after which the fields will be expired in {@link Duration#toMillis() milliseconds}
+ * precision, must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time
+ * is set/updated; {@code 0} indicating the expiration time is not set; {@code -2} indicating there is no such
+ * field; {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: HPEXPIRE
+ * @since 3.5
+ */
+ @Nullable
+ default List hpExpire(byte[] key, Duration ttl, byte[]... fields) {
+ return hpExpire(key, ttl.toMillis(), fields);
+ }
+
+ /**
+ * Set time to live for given {@code fields} in milliseconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param millis the amount of time after which the fields will be expired in milliseconds, must not be
+ * {@literal null}.
+ * @param condition the condition for expiration, must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time
+ * is set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition
+ * is not met); {@code -2} indicating there is no such field; {@literal null} when used in pipeline /
+ * transaction.
+ * @see Redis Documentation: HPEXPIRE
+ * @since 3.5
+ */
+ @Nullable
+ List hpExpire(byte[] key, long millis, ExpirationOptions.Condition condition, byte[]... fields);
+
+ /**
+ * Set the expiration for given {@code field} as a {@literal UNIX} timestamp.
+ *
+ * @param key must not be {@literal null}.
+ * @param unixTime the moment in time in which the field expires, must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is in the past; {@code 1} indicating
+ * expiration time is set/updated; {@code 0} indicating the expiration time is not set; {@code -2} indicating
+ * there is no such field; {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: HEXPIREAT
+ * @since 3.5
+ */
+ @Nullable
+ default List hExpireAt(byte[] key, long unixTime, byte[]... fields) {
+ return hExpireAt(key, unixTime, ExpirationOptions.Condition.ALWAYS, fields);
+ }
+
+ /**
+ * Set the expiration for given {@code field} as a {@literal UNIX} timestamp.
+ *
+ * @param key must not be {@literal null}.
+ * @param unixTime the moment in time in which the field expires, must not be {@literal null}.
+ * @param condition the condition for expiration, must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is in the past; {@code 1} indicating
+ * expiration time is set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX |
+ * GT | LT condition is not met); {@code -2} indicating there is no such field; {@literal null} when used in
+ * pipeline / transaction.
+ * @see Redis Documentation: HEXPIREAT
+ * @since 3.5
+ */
+ @Nullable
+ List hExpireAt(byte[] key, long unixTime, ExpirationOptions.Condition condition, byte[]... fields);
+
+ /**
+ * Set the expiration for given {@code field} as a {@literal UNIX} timestamp in milliseconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param unixTimeInMillis the moment in time in which the field expires in milliseconds, must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is in the past; {@code 1} indicating
+ * expiration time is set/updated; {@code 0} indicating the expiration time is not set; {@code -2} indicating
+ * there is no such field; {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: HPEXPIREAT
+ * @since 3.5
+ */
+ @Nullable
+ default List hpExpireAt(byte[] key, long unixTimeInMillis, byte[]... fields) {
+ return hpExpireAt(key, unixTimeInMillis, ExpirationOptions.Condition.ALWAYS, fields);
+ }
+
+ /**
+ * Set the expiration for given {@code field} as a {@literal UNIX} timestamp in milliseconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param unixTimeInMillis the moment in time in which the field expires in milliseconds, must not be {@literal null}.
+ * @param condition the condition for expiration, must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is in the past; {@code 1} indicating
+ * expiration time is set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX |
+ * GT | LT condition is not met); {@code -2} indicating there is no such field; {@literal null} when used in
+ * pipeline / transaction.
+ * @see Redis Documentation: HPEXPIREAT
+ * @since 3.5
+ */
+ @Nullable
+ List hpExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition,
+ byte[]... fields);
+
+ /**
+ * Remove the expiration from given {@code field}.
+ *
+ * @param key must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: {@code 1} indicating expiration time is
+ * removed; {@code -1} field has no expiration time to be removed; {@code -2} indicating there is no such
+ * field; {@literal null} when used in pipeline / transaction.{@literal null} when used in pipeline /
+ * transaction.
+ * @see Redis Documentation: HPERSIST
+ * @since 3.5
+ */
+ @Nullable
+ List hPersist(byte[] key, byte[]... fields);
+
+ /**
+ * Get the time to live for {@code fields} in seconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: the time to live in seconds; or a negative
+ * value to signal an error. The command returns {@code -1} if the field exists but has no associated
+ * expiration time. The command returns {@code -2} if the field does not exist; {@literal null} when used in
+ * pipeline / transaction.
+ * @see Redis Documentation: HTTL
+ * @since 3.5
+ */
+ @Nullable
+ List hTtl(byte[] key, byte[]... fields);
+
+ /**
+ * Get the time to live for {@code fields} in and convert it to the given {@link TimeUnit}.
+ *
+ * @param key must not be {@literal null}.
+ * @param timeUnit must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return for each of the fields supplied - the time to live in the {@link TimeUnit} provided; or a negative value to
+ * signal an error. The command returns {@code -1} if the key exists but has no associated expiration time.
+ * The command returns {@code -2} if the key does not exist; {@literal null} when used in pipeline /
+ * transaction.
+ * @see Redis Documentation: HTTL
+ * @since 3.5
+ */
+ @Nullable
+ List hTtl(byte[] key, TimeUnit timeUnit, byte[]... fields);
+
+ /**
+ * Get the time to live for {@code fields} in milliseconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: the time to live in seconds; or a negative
+ * value to signal an error. The command returns {@code -1} if the key exists but has no associated expiration
+ * time. The command returns {@code -2} if the key does not exist; {@literal null} when used in pipeline /
+ * transaction.
+ * @see Redis Documentation: HTTL
+ * @since 3.5
+ */
+ @Nullable
+ List hpTtl(byte[] key, byte[]... fields);
}
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisHyperLogLogCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisHyperLogLogCommands.java
index de24aff2d7..67fa08a4ad 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisHyperLogLogCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisHyperLogLogCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014-2024 the original author or authors.
+ * Copyright 2014-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisInvalidSubscriptionException.java b/src/main/java/org/springframework/data/redis/connection/RedisInvalidSubscriptionException.java
index 0af5905aec..d638a90de7 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisInvalidSubscriptionException.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisInvalidSubscriptionException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java
index ceaf0025be..4319dd8705 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.data.redis.connection;
import java.time.Duration;
+import java.time.Instant;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -25,6 +26,7 @@
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
/**
* Key-specific commands supported by Redis.
@@ -180,49 +182,237 @@ default Cursor scan(KeyScanOptions options) {
@Nullable
Boolean renameNX(byte[] oldKey, byte[] newKey);
+ /**
+ * @param key must not be {@literal null}.
+ * @param expiration the {@link org.springframework.data.redis.core.types.Expiration} to apply.
+ * @param options additional options to be sent along with the command.
+ * @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
+ * {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
+ * skipped because of the provided arguments.
+ * @since 3.5
+ * @see Redis Documentation: EXPIRE
+ * @see Redis Documentation: PEXPIRE
+ * @see Redis Documentation: EXPIREAT
+ * @see Redis Documentation: PEXPIREAT
+ * @see Redis Documentation: PERSIST
+ */
+ @Nullable
+ default Boolean applyExpiration(byte[] key, org.springframework.data.redis.core.types.Expiration expiration,
+ ExpirationOptions options) {
+
+ if (expiration.isPersistent()) {
+ return persist(key);
+ }
+
+ if (ObjectUtils.nullSafeEquals(ExpirationOptions.none(), options)) {
+ if (ObjectUtils.nullSafeEquals(TimeUnit.MILLISECONDS, expiration.getTimeUnit())) {
+ if (expiration.isUnixTimestamp()) {
+ return expireAt(key, expiration.getExpirationTimeInMilliseconds());
+ }
+ return expire(key, expiration.getExpirationTimeInMilliseconds());
+ }
+ if (expiration.isUnixTimestamp()) {
+ return expireAt(key, expiration.getExpirationTimeInSeconds());
+ }
+ return expire(key, expiration.getExpirationTimeInSeconds());
+ }
+
+ if (ObjectUtils.nullSafeEquals(TimeUnit.MILLISECONDS, expiration.getTimeUnit())) {
+ if (expiration.isUnixTimestamp()) {
+ return expireAt(key, expiration.getExpirationTimeInMilliseconds(), options.getCondition());
+ }
+
+ return expire(key, expiration.getExpirationTimeInMilliseconds(), options.getCondition());
+ }
+
+ if (expiration.isUnixTimestamp()) {
+ return expireAt(key, expiration.getExpirationTimeInSeconds(), options.getCondition());
+ }
+
+ return expire(key, expiration.getExpirationTimeInSeconds(), options.getCondition());
+ }
+
/**
* Set time to live for given {@code key} in seconds.
*
* @param key must not be {@literal null}.
* @param seconds
- * @return {@literal null} when used in pipeline / transaction.
+ * @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
+ * {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
+ * skipped because of the provided arguments.
* @see Redis Documentation: EXPIRE
*/
@Nullable
- Boolean expire(byte[] key, long seconds);
+ default Boolean expire(byte[] key, long seconds) {
+ return expire(key, seconds, ExpirationOptions.Condition.ALWAYS);
+ }
+
+ /**
+ * Set time to live for given {@code key} in seconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param seconds
+ * @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
+ * {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
+ * skipped because of the provided arguments.
+ * @see Redis Documentation: EXPIRE
+ * @since 3.5
+ */
+ @Nullable
+ Boolean expire(byte[] key, long seconds, ExpirationOptions.Condition condition);
+
+ /**
+ * Set time to live for given {@code key} using {@link Duration#toSeconds() seconds} precision.
+ *
+ * @param key must not be {@literal null}.
+ * @param duration
+ * @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
+ * {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
+ * skipped because of the provided arguments.
+ * @see Redis Documentation: EXPIRE
+ * @since 3.5
+ */
+ @Nullable
+ default Boolean expire(byte[] key, Duration duration) {
+ return expire(key, duration.toSeconds());
+ }
/**
* Set time to live for given {@code key} in milliseconds.
*
* @param key must not be {@literal null}.
* @param millis
- * @return {@literal null} when used in pipeline / transaction.
+ * @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
+ * {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
+ * skipped because of the provided arguments.
+ * @see Redis Documentation: PEXPIRE
+ */
+ @Nullable
+ default Boolean pExpire(byte[] key, long millis) {
+ return pExpire(key, millis, ExpirationOptions.Condition.ALWAYS);
+ }
+
+ /**
+ * Set time to live for given {@code key} in milliseconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param millis
+ * @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
+ * {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
+ * skipped because of the provided arguments.
* @see Redis Documentation: PEXPIRE
+ * @since 3.5
*/
@Nullable
- Boolean pExpire(byte[] key, long millis);
+ Boolean pExpire(byte[] key, long millis, ExpirationOptions.Condition condition);
+
+ /**
+ * Set time to live for given {@code key} using {@link Duration#toMillis() milliseconds} precision.
+ *
+ * @param key must not be {@literal null}.
+ * @param duration
+ * @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
+ * {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
+ * skipped because of the provided arguments.
+ * @see Redis Documentation: PEXPIRE
+ * @since 3.5
+ */
+ @Nullable
+ default Boolean pExpire(byte[] key, Duration duration) {
+ return pExpire(key, duration.toMillis());
+ }
/**
* Set the expiration for given {@code key} as a {@literal UNIX} timestamp.
*
* @param key must not be {@literal null}.
* @param unixTime
- * @return {@literal null} when used in pipeline / transaction.
+ * @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
+ * {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
+ * skipped because of the provided arguments.
+ * @see Redis Documentation: EXPIREAT
+ */
+ @Nullable
+ default Boolean expireAt(byte[] key, long unixTime) {
+ return expireAt(key, unixTime, ExpirationOptions.Condition.ALWAYS);
+ }
+
+ /**
+ * Set the expiration for given {@code key} as a {@literal UNIX} timestamp.
+ *
+ * @param key must not be {@literal null}.
+ * @param unixTime
+ * @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
+ * {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
+ * skipped because of the provided arguments.
+ * @see Redis Documentation: EXPIREAT
+ * @since 3.5
+ */
+ @Nullable
+ Boolean expireAt(byte[] key, long unixTime, ExpirationOptions.Condition condition);
+
+ /**
+ * Set the expiration for given {@code key} as a {@literal UNIX} timestamp in {@link Instant#getEpochSecond() seconds}
+ * precision.
+ *
+ * @param key must not be {@literal null}.
+ * @param unixTime
+ * @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
+ * {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
+ * skipped because of the provided arguments.
* @see Redis Documentation: EXPIREAT
+ * @since 3.5
*/
@Nullable
- Boolean expireAt(byte[] key, long unixTime);
+ default Boolean expireAt(byte[] key, Instant unixTime) {
+ return expireAt(key, unixTime.getEpochSecond());
+ }
/**
* Set the expiration for given {@code key} as a {@literal UNIX} timestamp in milliseconds.
*
* @param key must not be {@literal null}.
* @param unixTimeInMillis
- * @return {@literal null} when used in pipeline / transaction.
+ * @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
+ * {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
+ * skipped because of the provided arguments.
+ * @see Redis Documentation: PEXPIREAT
+ */
+ @Nullable
+ default Boolean pExpireAt(byte[] key, long unixTimeInMillis) {
+ return pExpireAt(key, unixTimeInMillis, ExpirationOptions.Condition.ALWAYS);
+ }
+
+ /**
+ * Set the expiration for given {@code key} as a {@literal UNIX} timestamp in milliseconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param unixTimeInMillis
+ * @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
+ * {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
+ * skipped because of the provided arguments.
* @see Redis Documentation: PEXPIREAT
+ * @since 3.5
*/
@Nullable
- Boolean pExpireAt(byte[] key, long unixTimeInMillis);
+ Boolean pExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition);
+
+ /**
+ * Set the expiration for given {@code key} as a {@literal UNIX} timestamp in {@link Instant#toEpochMilli()
+ * milliseconds} precision.
+ *
+ * @param key must not be {@literal null}.
+ * @param unixTime
+ * @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
+ * {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
+ * skipped because of the provided arguments.
+ * @see Redis Documentation: PEXPIREAT
+ * @since 3.5
+ */
+ @Nullable
+ default Boolean pExpireAt(byte[] key, Instant unixTime) {
+ return pExpireAt(key, unixTime.toEpochMilli());
+ }
/**
* Remove the expiration from given {@code key}.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisListCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisListCommands.java
index 8d174c89b8..18852d2f17 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisListCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisListCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisNode.java b/src/main/java/org/springframework/data/redis/connection/RedisNode.java
index ad6841054f..3e117e0d36 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisNode.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisNode.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014-2024 the original author or authors.
+ * Copyright 2014-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,10 +24,14 @@
* @author Christoph Strobl
* @author Thomas Darimont
* @author Mark Paluch
+ * @author LeeHyungGeol
* @since 1.4
*/
public class RedisNode implements NamedNode {
+ public static final int DEFAULT_PORT = 6379;
+ public static final int DEFAULT_SENTINEL_PORT = 26379;
+
@Nullable String id;
@Nullable String name;
@Nullable String host;
@@ -66,8 +70,11 @@ private RedisNode(RedisNode redisNode) {
* the port. For example:
*
*
+ * RedisNode.fromString("127.0.0.1");
* RedisNode.fromString("127.0.0.1:6379");
+ * RedisNode.fromString("[aaaa:bbbb::dddd:eeee]");
* RedisNode.fromString("[aaaa:bbbb::dddd:eeee]:6379");
+ * RedisNode.fromString("my.redis.server");
* RedisNode.fromString("my.redis.server:6379");
*
*
@@ -76,6 +83,27 @@ private RedisNode(RedisNode redisNode) {
* @since 2.7.4
*/
public static RedisNode fromString(String hostPortString) {
+ return fromString(hostPortString, DEFAULT_PORT);
+ }
+
+ /**
+ * Parse a {@code hostAndPort} string into {@link RedisNode}. Supports IPv4, IPv6, and hostname notations including
+ * the port. For example:
+ *
+ *
+ * RedisNode.fromString("127.0.0.1");
+ * RedisNode.fromString("127.0.0.1:6379");
+ * RedisNode.fromString("[aaaa:bbbb::dddd:eeee]");
+ * RedisNode.fromString("[aaaa:bbbb::dddd:eeee]:6379");
+ * RedisNode.fromString("my.redis.server");
+ * RedisNode.fromString("my.redis.server:6379");
+ *
+ *
+ * @param hostPortString must not be {@literal null} or empty.
+ * @return the parsed {@link RedisNode}.
+ * @since 3.4
+ */
+ public static RedisNode fromString(String hostPortString, int defaultPort) {
Assert.notNull(hostPortString, "HostAndPort must not be null");
@@ -94,19 +122,31 @@ public static RedisNode fromString(String hostPortString) {
portString = hostPortString.substring(colonPos + 1);
} else {
// 0 or 2+ colons. Bare hostname or IPv6 literal.
- host = hostPortString;
+ int lastColonIndex = hostPortString.lastIndexOf(':');
+
+ // IPv6 literal
+ if (lastColonIndex > hostPortString.indexOf(']')) {
+ host = hostPortString.substring(0, lastColonIndex);
+ portString = hostPortString.substring(lastColonIndex + 1);
+ } else {
+ // bare hostname
+ host = hostPortString;
+ }
}
}
- int port = -1;
- try {
- port = Integer.parseInt(portString);
- } catch (RuntimeException ignore) {
- throw new IllegalArgumentException(String.format("Unparseable port number: %s", hostPortString));
+ int port = defaultPort;
+
+ if (StringUtils.hasText(portString)) {
+ try {
+ port = Integer.parseInt(portString);
+ } catch (RuntimeException ignore) {
+ throw new IllegalArgumentException("Unparseable port number: %s".formatted(hostPortString));
+ }
}
if (!isValidPort(port)) {
- throw new IllegalArgumentException(String.format("Port number out of range: %s", hostPortString));
+ throw new IllegalArgumentException("Port number out of range: %s".formatted(hostPortString));
}
return new RedisNode(host, port);
@@ -123,14 +163,14 @@ private static String[] getHostAndPortFromBracketedHost(String hostPortString) {
if (hostPortString.charAt(0) != '[') {
throw new IllegalArgumentException(
- String.format("Bracketed host-port string must start with a bracket: %s", hostPortString));
+ "Bracketed host-port string must start with a bracket: %s".formatted(hostPortString));
}
int colonIndex = hostPortString.indexOf(':');
int closeBracketIndex = hostPortString.lastIndexOf(']');
if (!(colonIndex > -1 && closeBracketIndex > colonIndex)) {
- throw new IllegalArgumentException(String.format("Invalid bracketed host/port: %s", hostPortString));
+ throw new IllegalArgumentException("Invalid bracketed host/port: %s".formatted(hostPortString));
}
String host = hostPortString.substring(1, closeBracketIndex);
@@ -138,12 +178,11 @@ private static String[] getHostAndPortFromBracketedHost(String hostPortString) {
return new String[] { host, "" };
} else {
if (!(hostPortString.charAt(closeBracketIndex + 1) == ':')) {
- throw new IllegalArgumentException(
- String.format("Only a colon may follow a close bracket: %s", hostPortString));
+ throw new IllegalArgumentException("Only a colon may follow a close bracket: %s".formatted(hostPortString));
}
for (int i = closeBracketIndex + 2; i < hostPortString.length(); ++i) {
if (!Character.isDigit(hostPortString.charAt(i))) {
- throw new IllegalArgumentException(String.format("Port must be numeric: %s", hostPortString));
+ throw new IllegalArgumentException("Port must be numeric: %s".formatted(hostPortString));
}
}
return new String[] { host, hostPortString.substring(closeBracketIndex + 2) };
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisPassword.java b/src/main/java/org/springframework/data/redis/connection/RedisPassword.java
index 89c8713ea9..3fcb4ce343 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisPassword.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisPassword.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2024 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -140,7 +140,7 @@ public Optional toOptional() {
@Override
public String toString() {
- return String.format("%s[%s]", getClass().getSimpleName(), isPresent() ? "*****" : "");
+ return "%s[%s]".formatted(getClass().getSimpleName(), isPresent() ? "*****" : "");
}
@Override
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisPipelineException.java b/src/main/java/org/springframework/data/redis/connection/RedisPipelineException.java
index 9a6e31ca16..0f4cd4c9e5 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisPipelineException.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisPipelineException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisPubSubCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisPubSubCommands.java
index 6f25eb7daa..00283e80ce 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisPubSubCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisPubSubCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisScriptingCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisScriptingCommands.java
index 1fd9e96afd..ce54c205cd 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisScriptingCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisScriptingCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2024 the original author or authors.
+ * Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisSentinelCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisSentinelCommands.java
index 25f3fb666f..0a3882e643 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisSentinelCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisSentinelCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014-2024 the original author or authors.
+ * Copyright 2014-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisSentinelConfiguration.java b/src/main/java/org/springframework/data/redis/connection/RedisSentinelConfiguration.java
index 24b390724d..5839a8f701 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisSentinelConfiguration.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisSentinelConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014-2024 the original author or authors.
+ * Copyright 2014-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -77,8 +77,9 @@ public RedisSentinelConfiguration() {
/**
* Creates a new {@link RedisSentinelConfiguration} for given {@link String hostPort} combinations.
*
- *
- * sentinelHostAndPorts[0] = 127.0.0.1:23679 sentinelHostAndPorts[1] = 127.0.0.1:23680 ...
+ *
+ * sentinelHostAndPorts[0] = 127.0.0.1:23679
+ * sentinelHostAndPorts[1] = 127.0.0.1:23680 ...
*
*
* @param sentinelHostAndPorts must not be {@literal null}.
@@ -92,11 +93,9 @@ public RedisSentinelConfiguration(String master, Set sentinelHostAndPort
* Creates a new {@link RedisSentinelConfiguration} looking up configuration values from the given
* {@link PropertySource}.
*
- *
- *
+ *
* spring.redis.sentinel.master=myMaster
* spring.redis.sentinel.nodes=127.0.0.1:23679,127.0.0.1:23680,127.0.0.1:23681
- *
*
*
* @param propertySource must not be {@literal null}.
@@ -150,7 +149,7 @@ public RedisSentinelConfiguration(PropertySource> propertySource) {
try {
database = Integer.parseInt(databaseSource);
} catch (NumberFormatException ex) {
- throw new IllegalArgumentException(String.format("Invalid DB index '%s'; integer required", databaseSource));
+ throw new IllegalArgumentException("Invalid DB index '%s'; integer required".formatted(databaseSource));
}
this.setDatabase(database);
}
@@ -254,7 +253,7 @@ public RedisSentinelConfiguration sentinel(String host, Integer port) {
private void appendSentinels(Set hostAndPorts) {
for (String hostAndPort : hostAndPorts) {
- addSentinel(RedisNode.fromString(hostAndPort));
+ addSentinel(RedisNode.fromString(hostAndPort, RedisNode.DEFAULT_SENTINEL_PORT));
}
}
@@ -266,7 +265,7 @@ public int getDatabase() {
@Override
public void setDatabase(int index) {
- Assert.isTrue(index >= 0, () -> String.format("Invalid DB index '%d'; non-negative index required", index));
+ Assert.isTrue(index >= 0, "Invalid DB index '%d'; non-negative index required".formatted(index));
this.database = index;
}
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisSentinelConnection.java b/src/main/java/org/springframework/data/redis/connection/RedisSentinelConnection.java
index 0d84cc2107..34f0db6003 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisSentinelConnection.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisSentinelConnection.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014-2024 the original author or authors.
+ * Copyright 2014-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisServer.java b/src/main/java/org/springframework/data/redis/connection/RedisServer.java
index 1a8b306c45..bd363598c6 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisServer.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisServer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014-2024 the original author or authors.
+ * Copyright 2014-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisServerCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisServerCommands.java
index 26a610e05c..f21e6281fd 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisServerCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisServerCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisSetCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisSetCommands.java
index bd3e75deb8..26dcb1976f 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisSetCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisSetCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisSocketConfiguration.java b/src/main/java/org/springframework/data/redis/connection/RedisSocketConfiguration.java
index 7e70cfdfd4..e48799a659 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisSocketConfiguration.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisSocketConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2024 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -74,7 +74,7 @@ public int getDatabase() {
@Override
public void setDatabase(int index) {
- Assert.isTrue(index >= 0, () -> String.format("Invalid DB index '%s' (a positive index required)", index));
+ Assert.isTrue(index >= 0, () -> "Invalid DB index '%s'; non-negative index required".formatted(index));
this.database = index;
}
@@ -108,10 +108,9 @@ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
- if (!(o instanceof RedisSocketConfiguration)) {
+ if (!(o instanceof RedisSocketConfiguration that)) {
return false;
}
- RedisSocketConfiguration that = (RedisSocketConfiguration) o;
if (database != that.database) {
return false;
}
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisStandaloneConfiguration.java b/src/main/java/org/springframework/data/redis/connection/RedisStandaloneConfiguration.java
index 0ec52a6b70..8acb2a3be8 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisStandaloneConfiguration.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisStandaloneConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2024 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -70,7 +70,7 @@ public RedisStandaloneConfiguration(String hostName, int port) {
Assert.hasText(hostName, "Host name must not be null or empty");
Assert.isTrue(port >= 1 && port <= 65535,
- () -> String.format("Port %d must be a valid TCP port in the range between 1-65535", port));
+ "Port %d must be a valid TCP port in the range between 1-65535".formatted(port));
this.hostName = hostName;
this.port = port;
@@ -103,7 +103,7 @@ public int getDatabase() {
@Override
public void setDatabase(int index) {
- Assert.isTrue(index >= 0, () -> String.format("Invalid DB index '%s' (a positive index required)", index));
+ Assert.isTrue(index >= 0, "Invalid DB index '%d'; non-negative index required".formatted(index));
this.database = index;
}
@@ -137,10 +137,9 @@ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
- if (!(o instanceof RedisStandaloneConfiguration)) {
+ if (!(o instanceof RedisStandaloneConfiguration that)) {
return false;
}
- RedisStandaloneConfiguration that = (RedisStandaloneConfiguration) o;
if (port != that.port) {
return false;
}
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisStaticMasterReplicaConfiguration.java b/src/main/java/org/springframework/data/redis/connection/RedisStaticMasterReplicaConfiguration.java
index 6ed05903b1..66dd6f6051 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisStaticMasterReplicaConfiguration.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisStaticMasterReplicaConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-2024 the original author or authors.
+ * Copyright 2018-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -118,7 +118,7 @@ public int getDatabase() {
@Override
public void setDatabase(int index) {
- Assert.isTrue(index >= 0, () -> String.format("Invalid DB index '%s' (a positive index required)", index));
+ Assert.isTrue(index >= 0, "Invalid DB index '%d'; non-negative index required".formatted(index));
this.database = index;
this.nodes.forEach(it -> it.setDatabase(database));
@@ -159,10 +159,9 @@ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
- if (!(o instanceof RedisStaticMasterReplicaConfiguration)) {
+ if (!(o instanceof RedisStaticMasterReplicaConfiguration that)) {
return false;
}
- RedisStaticMasterReplicaConfiguration that = (RedisStaticMasterReplicaConfiguration) o;
if (database != that.database) {
return false;
}
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisStreamCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisStreamCommands.java
index a326106610..8385d70d34 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisStreamCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisStreamCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-2024 the original author or authors.
+ * Copyright 2018-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -214,7 +214,7 @@ public Long getMaxlen() {
* @return {@literal true} if {@literal MAXLEN} is set.
*/
public boolean hasMaxlen() {
- return maxlen != null && maxlen > 0;
+ return maxlen != null;
}
/**
@@ -246,10 +246,9 @@ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
- if (!(o instanceof XAddOptions)) {
+ if (!(o instanceof XAddOptions that)) {
return false;
}
- XAddOptions that = (XAddOptions) o;
if (nomkstream != that.nomkstream) {
return false;
}
@@ -789,19 +788,28 @@ public static XPendingOptions unbounded() {
/**
* Create new {@link XPendingOptions} with an unbounded {@link Range} ({@literal - +}).
*
- * @param count the max number of messages to return. Must not be {@literal null}.
+ * @param count the max number of messages to return. Must not be negative.
* @return new instance of {@link XPendingOptions}.
*/
public static XPendingOptions unbounded(Long count) {
+
+ Assert.isTrue(count > -1, "Count must not be negative");
+
return new XPendingOptions(null, Range.unbounded(), count);
}
/**
* Create new {@link XPendingOptions} with given {@link Range} and limit.
*
+ * @param range must not be {@literal null}.
+ * @param count the max number of messages to return. Must not be negative.
* @return new instance of {@link XPendingOptions}.
*/
public static XPendingOptions range(Range> range, Long count) {
+
+ Assert.notNull(range, "Range must not be null");
+ Assert.isTrue(count > -1, "Count must not be negative");
+
return new XPendingOptions(null, range, count);
}
@@ -849,7 +857,7 @@ public boolean hasConsumer() {
* @return {@literal true} count is set.
*/
public boolean isLimited() {
- return count != null && count > -1;
+ return count != null;
}
}
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java
index beac673984..f591be4245 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@
* @author Costin Leau
* @author Christoph Strobl
* @author Mark Paluch
+ * @author Marcin Grzejszczak
*/
public interface RedisStringCommands {
@@ -86,10 +87,10 @@ enum BitOperation {
/**
* Get multiple {@code keys}. Values are in the order of the requested keys Absent field values are represented using
- * {@code null} in the resulting {@link List}.
+ * {@literal null} in the resulting {@link List}.
*
* @param keys must not be {@literal null}.
- * @return {@code null} when used in pipeline / transaction.
+ * @return {@literal null} when used in pipeline / transaction.
* @see Redis Documentation: MGET
*/
@Nullable
@@ -122,6 +123,22 @@ enum BitOperation {
@Nullable
Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption option);
+ /**
+ * Set {@code value} for {@code key}. Return the old string stored at key, or {@literal null} if key did not exist. An
+ * error is returned and SET aborted if the value stored at key is not a string.
+ *
+ * @param key must not be {@literal null}.
+ * @param value must not be {@literal null}.
+ * @param expiration must not be {@literal null}. Use {@link Expiration#persistent()} to not set any ttl or
+ * {@link Expiration#keepTtl()} to keep the existing expiration.
+ * @param option must not be {@literal null}. Use {@link SetOption#upsert()} to add non-existing.
+ * @return {@literal null} when used in pipeline / transaction.
+ * @since 3.5
+ * @see Redis Documentation: SET
+ */
+ @Nullable
+ byte[] setGet(byte[] key, byte[] value, Expiration expiration, SetOption option);
+
/**
* Set {@code value} for {@code key}, only if {@code key} does not exist.
*
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisSubscribedConnectionException.java b/src/main/java/org/springframework/data/redis/connection/RedisSubscribedConnectionException.java
index 56ce5a9f35..846a935c4d 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisSubscribedConnectionException.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisSubscribedConnectionException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisTxCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisTxCommands.java
index 8e3578dbc2..56ad3634ea 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisTxCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisTxCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java
index 8578d688f6..6ad0ecd59b 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ReturnType.java b/src/main/java/org/springframework/data/redis/connection/ReturnType.java
index 5ed9e24dbf..b39a9684ce 100644
--- a/src/main/java/org/springframework/data/redis/connection/ReturnType.java
+++ b/src/main/java/org/springframework/data/redis/connection/ReturnType.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2024 the original author or authors.
+ * Copyright 2013-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,12 +46,12 @@ public enum ReturnType {
MULTI,
/**
- * Returned as {@literal byte[]}
+ * Returned as {@code byte[]}
*/
STATUS,
/**
- * Returned as {@literal byte[]}
+ * Returned as {@code byte[]}
*/
VALUE;
@@ -73,7 +73,11 @@ public static ReturnType fromJavaType(@Nullable Class> javaType) {
return ReturnType.BOOLEAN;
}
- if (ClassUtils.isAssignable(Long.class, javaType)) {
+ if (ClassUtils.isAssignable(Double.class, javaType) || ClassUtils.isAssignable(Float.class, javaType)) {
+ return ReturnType.VALUE;
+ }
+
+ if (ClassUtils.isAssignable(Number.class, javaType)) {
return ReturnType.INTEGER;
}
diff --git a/src/main/java/org/springframework/data/redis/connection/SentinelMasterId.java b/src/main/java/org/springframework/data/redis/connection/SentinelMasterId.java
index e5d72b171c..ce778884d7 100644
--- a/src/main/java/org/springframework/data/redis/connection/SentinelMasterId.java
+++ b/src/main/java/org/springframework/data/redis/connection/SentinelMasterId.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021-2024 the original author or authors.
+ * Copyright 2021-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,10 +51,9 @@ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
- if (!(o instanceof SentinelMasterId)) {
+ if (!(o instanceof SentinelMasterId that)) {
return false;
}
- SentinelMasterId that = (SentinelMasterId) o;
return ObjectUtils.nullSafeEquals(name, that.name);
}
diff --git a/src/main/java/org/springframework/data/redis/connection/SortParameters.java b/src/main/java/org/springframework/data/redis/connection/SortParameters.java
index b90d56c6a9..27804b98fe 100644
--- a/src/main/java/org/springframework/data/redis/connection/SortParameters.java
+++ b/src/main/java/org/springframework/data/redis/connection/SortParameters.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java
index e198eecfd3..dfcc585130 100644
--- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java
+++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -71,7 +71,6 @@
* @author Andrey Shlykov
* @author ihaohong
* @author Shyngys Sapraliyev
- *
* @see RedisCallback
* @see RedisSerializer
* @see StringRedisTemplate
@@ -225,29 +224,74 @@ interface StringTuple extends Tuple {
* @see Redis Documentation: EXPIRE
* @see RedisKeyCommands#expire(byte[], long)
*/
- Boolean expire(String key, long seconds);
+ default Boolean expire(String key, long seconds) {
+ return expire(key, seconds, ExpirationOptions.Condition.ALWAYS);
+ }
+
+ /**
+ * Set time to live for given {@code key} in seconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param condition the condition for expiration, must not be {@literal null}.
+ * @param seconds
+ * @return
+ * @since 3.5
+ * @see Redis Documentation: EXPIRE
+ * @see RedisKeyCommands#expire(byte[], long)
+ */
+ Boolean expire(String key, long seconds, ExpirationOptions.Condition condition);
+
+ /**
+ * Set time to live for given {@code key} in milliseconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param millis
+ * @return
+ * @see Redis Documentation: PEXPIRE
+ * @see RedisKeyCommands#pExpire(byte[], long)
+ */
+ default Boolean pExpire(String key, long millis) {
+ return pExpire(key, millis, ExpirationOptions.Condition.ALWAYS);
+ }
/**
* Set time to live for given {@code key} in milliseconds.
*
* @param key must not be {@literal null}.
* @param millis
+ * @param condition the condition for expiration, must not be {@literal null}.
* @return
+ * @since 3.5
* @see Redis Documentation: PEXPIRE
* @see RedisKeyCommands#pExpire(byte[], long)
*/
- Boolean pExpire(String key, long millis);
+ Boolean pExpire(String key, long millis, ExpirationOptions.Condition condition);
+
+ /**
+ * Set the expiration for given {@code key} as a {@literal UNIX} timestamp.
+ *
+ * @param key must not be {@literal null}.
+ * @param unixTime
+ * @return
+ * @see Redis Documentation: EXPIREAT
+ * @see RedisKeyCommands#expireAt(byte[], long)
+ */
+ default Boolean expireAt(String key, long unixTime) {
+ return expireAt(key, unixTime, ExpirationOptions.Condition.ALWAYS);
+ }
/**
* Set the expiration for given {@code key} as a {@literal UNIX} timestamp.
*
* @param key must not be {@literal null}.
* @param unixTime
+ * @param condition the condition for expiration, must not be {@literal null}.
* @return
+ * @since 3.5
* @see Redis Documentation: EXPIREAT
* @see RedisKeyCommands#expireAt(byte[], long)
*/
- Boolean expireAt(String key, long unixTime);
+ Boolean expireAt(String key, long unixTime, ExpirationOptions.Condition condition);
/**
* Set the expiration for given {@code key} as a {@literal UNIX} timestamp in milliseconds.
@@ -258,7 +302,22 @@ interface StringTuple extends Tuple {
* @see Redis Documentation: PEXPIREAT
* @see RedisKeyCommands#pExpireAt(byte[], long)
*/
- Boolean pExpireAt(String key, long unixTimeInMillis);
+ default Boolean pExpireAt(String key, long unixTimeInMillis) {
+ return pExpireAt(key, unixTimeInMillis, ExpirationOptions.Condition.ALWAYS);
+ }
+
+ /**
+ * Set the expiration for given {@code key} as a {@literal UNIX} timestamp in milliseconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param unixTimeInMillis
+ * @param condition the condition for expiration, must not be {@literal null}.
+ * @return
+ * @since 3.5
+ * @see Redis Documentation: PEXPIREAT
+ * @see RedisKeyCommands#pExpireAt(byte[], long)
+ */
+ Boolean pExpireAt(String key, long unixTimeInMillis, ExpirationOptions.Condition condition);
/**
* Remove the expiration from given {@code key}.
@@ -1661,7 +1720,6 @@ default Long lPos(String key, String element) {
*/
Long zRemRange(String key, long start, long end);
-
/**
* Remove all elements between the lexicographical {@link Range}.
*
@@ -1941,7 +1999,8 @@ default Set zUnionWithScores(Aggregate aggregate, int[] weights, St
* @return
* @since 1.6
* @see Redis Documentation: ZRANGEBYLEX
- * @see RedisZSetCommands#zRangeByLex(byte[], org.springframework.data.domain.Range, org.springframework.data.redis.connection.Limit)
+ * @see RedisZSetCommands#zRangeByLex(byte[], org.springframework.data.domain.Range,
+ * org.springframework.data.redis.connection.Limit)
*/
Set zRangeByLex(String key, org.springframework.data.domain.Range range,
org.springframework.data.redis.connection.Limit limit);
@@ -1983,7 +2042,8 @@ default Set zRevRangeByLex(String key, org.springframework.data.domain.R
* @return
* @since 2.4
* @see Redis Documentation: ZREVRANGEBYLEX
- * @see RedisZSetCommands#zRevRangeByLex(byte[], org.springframework.data.domain.Range, org.springframework.data.redis.connection.Limit)
+ * @see RedisZSetCommands#zRevRangeByLex(byte[], org.springframework.data.domain.Range,
+ * org.springframework.data.redis.connection.Limit)
*/
Set zRevRangeByLex(String key, org.springframework.data.domain.Range range,
org.springframework.data.redis.connection.Limit limit);
@@ -2333,6 +2393,208 @@ Long zRangeStoreRevByScore(String dstKey, String srcKey,
@Nullable
Long hStrLen(String key, String field);
+ /**
+ * Set time to live for given {@code field} in seconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param seconds the amount of time after which the key will be expired in seconds, must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time
+ * is set/updated; {@code 0} indicating the expiration time is not set; {@code -2} indicating there is no such
+ * field; {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: HEXPIRE
+ * @since 3.5
+ */
+ @Nullable
+ default List hExpire(String key, long seconds, String... fields) {
+ return hExpire(key, seconds, ExpirationOptions.Condition.ALWAYS, fields);
+ }
+
+ /**
+ * Set time to live for given {@code field} in seconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param seconds the amount of time after which the key will be expired in seconds, must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time
+ * is set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition
+ * is not met); {@code -2} indicating there is no such field; {@literal null} when used in pipeline /
+ * transaction.
+ * @see Redis Documentation: HEXPIRE
+ * @since 3.5
+ */
+ @Nullable
+ List hExpire(String key, long seconds, ExpirationOptions.Condition condition, String... fields);
+
+ /**
+ * Set time to live for given {@code field} in milliseconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param millis the amount of time after which the key will be expired in milliseconds, must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time
+ * is set/updated; {@code 0} indicating the expiration time is not set; {@code -2} indicating there is no such
+ * field; {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: HPEXPIRE
+ * @since 3.5
+ */
+ @Nullable
+ default List hpExpire(String key, long millis, String... fields) {
+ return hpExpire(key, millis, ExpirationOptions.Condition.ALWAYS, fields);
+ }
+
+ /**
+ * Set time to live for given {@code field} in milliseconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param millis the amount of time after which the key will be expired in milliseconds, must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time
+ * is set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition
+ * is not met); {@code -2} indicating there is no such field; {@literal null} when used in pipeline /
+ * transaction.
+ * @see Redis Documentation: HPEXPIRE
+ * @since 3.5
+ */
+ @Nullable
+ List hpExpire(String key, long millis, ExpirationOptions.Condition condition, String... fields);
+
+ /**
+ * Set the expiration for given {@code field} as a {@literal UNIX} timestamp.
+ *
+ * @param key must not be {@literal null}.
+ * @param unixTime the moment in time in which the field expires, must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is in the past; {@code 1} indicating
+ * expiration time is set/updated; {@code 0} indicating the expiration time is not set; {@code -2} indicating
+ * there is no such field; {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: HEXPIREAT
+ * @since 3.5
+ */
+ @Nullable
+ default List hExpireAt(String key, long unixTime, String... fields) {
+ return hExpireAt(key, unixTime, ExpirationOptions.Condition.ALWAYS, fields);
+ }
+
+ /**
+ * Set the expiration for given {@code field} as a {@literal UNIX} timestamp.
+ *
+ * @param key must not be {@literal null}.
+ * @param unixTime the moment in time in which the field expires, must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is in the past; {@code 1} indicating
+ * expiration time is set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX |
+ * GT | LT condition is not met); {@code -2} indicating there is no such field; {@literal null} when used in
+ * pipeline / transaction.
+ * @see Redis Documentation: HEXPIREAT
+ * @since 3.5
+ */
+ @Nullable
+ List hExpireAt(String key, long unixTime, ExpirationOptions.Condition condition, String... fields);
+
+ /**
+ * Set the expiration for given {@code field} as a {@literal UNIX} timestamp in milliseconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param unixTimeInMillis the moment in time in which the field expires in milliseconds, must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is in the past; {@code 1} indicating
+ * expiration time is set/updated; {@code 0} indicating the expiration time is not set; {@code -2} indicating
+ * there is no such field; {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: HPEXPIREAT
+ * @since 3.5
+ */
+ @Nullable
+ default List hpExpireAt(String key, long unixTimeInMillis, String... fields) {
+ return hpExpireAt(key, unixTimeInMillis, ExpirationOptions.Condition.ALWAYS, fields);
+ }
+
+ /**
+ * Set the expiration for given {@code field} as a {@literal UNIX} timestamp in milliseconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param unixTimeInMillis the moment in time in which the field expires in milliseconds, must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is
+ * deleted already due to expiration, or provided expiry interval is in the past; {@code 1} indicating
+ * expiration time is set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX |
+ * GT | LT condition is not met); {@code -2} indicating there is no such field; {@literal null} when used in
+ * pipeline / transaction.
+ * @see Redis Documentation: HPEXPIREAT
+ * @since 3.5
+ */
+ @Nullable
+ List hpExpireAt(String key, long unixTimeInMillis, ExpirationOptions.Condition condition,
+ String... fields);
+
+ /**
+ * Remove the expiration from given {@code field}.
+ *
+ * @param key must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: {@code 1} indicating expiration time is
+ * removed; {@code -1} field has no expiration time to be removed; {@code -2} indicating there is no such
+ * field; {@literal null} when used in pipeline / transaction.{@literal null} when used in pipeline /
+ * transaction.
+ * @see Redis Documentation: HPERSIST
+ * @since 3.5
+ */
+ @Nullable
+ List hPersist(String key, String... fields);
+
+ /**
+ * Get the time to live for {@code fields} in seconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: the time to live in milliseconds; or a
+ * negative value to signal an error. The command returns {@code -1} if the key exists but has no associated
+ * expiration time. The command returns {@code -2} if the key does not exist; {@literal null} when used in
+ * pipeline / transaction.
+ * @see Redis Documentation: HTTL
+ * @since 3.5
+ */
+ @Nullable
+ List hTtl(String key, String... fields);
+
+ /**
+ * Get the time to live for {@code fields} in and convert it to the given {@link TimeUnit}.
+ *
+ * @param key must not be {@literal null}.
+ * @param timeUnit must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: the time to live in the {@link TimeUnit}
+ * provided; or a negative value to signal an error. The command returns {@code -1} if the key exists but has
+ * no associated expiration time. The command returns {@code -2} if the key does not exist; {@literal null}
+ * when used in pipeline / transaction.
+ * @see Redis Documentation: HTTL
+ * @since 3.5
+ */
+ @Nullable
+ List hTtl(String key, TimeUnit timeUnit, String... fields);
+
+ /**
+ * Get the time to live for {@code fields} in seconds.
+ *
+ * @param key must not be {@literal null}.
+ * @param fields must not be {@literal null}.
+ * @return a list of {@link Long} values for each of the fields provided: the time to live in milliseconds; or a
+ * negative value to signal an error. The command returns {@code -1} if the key exists but has no associated
+ * expiration time. The command returns {@code -2} if the key does not exist; {@literal null} when used in
+ * pipeline / transaction.
+ * @see Redis Documentation: HTTL
+ * @since 3.5
+ */
+ @Nullable
+ List hpTtl(String key, String... fields);
+
// -------------------------------------------------------------------------
// Methods dealing with HyperLogLog
// -------------------------------------------------------------------------
@@ -2556,8 +2818,7 @@ GeoResults> geoRadiusByMember(String key, String member, Dis
/**
* Return the members of a geo set which are within the borders of the area specified by a given {@link GeoShape
- * shape}. The query's center point is provided by
- * {@link GeoReference}.
+ * shape}. The query's center point is provided by {@link GeoReference}.
*
* @param key must not be {@literal null}.
* @param reference must not be {@literal null}.
@@ -2573,8 +2834,7 @@ GeoResults> geoSearch(String key, GeoReference refer
/**
* Query the members of a geo set which are within the borders of the area specified by a given {@link GeoShape shape}
- * and store the result at {@code destKey}. The query's center point is provided by
- * {@link GeoReference}.
+ * and store the result at {@code destKey}. The query's center point is provided by {@link GeoReference}.
*
* @param key must not be {@literal null}.
* @param reference must not be {@literal null}.
diff --git a/src/main/java/org/springframework/data/redis/connection/Subscription.java b/src/main/java/org/springframework/data/redis/connection/Subscription.java
index 8fbbb32239..96510df79c 100644
--- a/src/main/java/org/springframework/data/redis/connection/Subscription.java
+++ b/src/main/java/org/springframework/data/redis/connection/Subscription.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2024 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/SubscriptionListener.java b/src/main/java/org/springframework/data/redis/connection/SubscriptionListener.java
index 5049ef2423..8d9e9d6b00 100644
--- a/src/main/java/org/springframework/data/redis/connection/SubscriptionListener.java
+++ b/src/main/java/org/springframework/data/redis/connection/SubscriptionListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021-2024 the original author or authors.
+ * Copyright 2021-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/ValueEncoding.java b/src/main/java/org/springframework/data/redis/connection/ValueEncoding.java
index 1185756bf3..b0d573639a 100644
--- a/src/main/java/org/springframework/data/redis/connection/ValueEncoding.java
+++ b/src/main/java/org/springframework/data/redis/connection/ValueEncoding.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-2024 the original author or authors.
+ * Copyright 2018-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/convert/Converters.java b/src/main/java/org/springframework/data/redis/connection/convert/Converters.java
index 98ec282faf..2d4eb2cb59 100644
--- a/src/main/java/org/springframework/data/redis/connection/convert/Converters.java
+++ b/src/main/java/org/springframework/data/redis/connection/convert/Converters.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2024 the original author or authors.
+ * Copyright 2013-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,11 +20,10 @@
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResult;
@@ -63,6 +62,7 @@
* @author daihuabin
* @author John Blum
* @author Sorokin Evgeniy
+ * @author Marcin Grzejszczak
*/
public abstract class Converters {
@@ -410,7 +410,7 @@ public static Object parse(Object source, String sourcePath, Map {
* {@code %s:%i} (Redis 3)
* {@code %s:%i@%i} (Redis 4, with bus port)
* {@code %s:%i@%i,%s} (Redis 7, with announced hostname)
+ *
+ * The output of the {@code CLUSTER NODES } command is just a space-separated CSV string, where each
+ * line represents a node in the cluster. The following is an example of output on Redis 7.2.0.
+ * You can check the latest here.
+ *
+ * {@code ... }
+ *
*
*/
- static final Pattern clusterEndpointPattern = Pattern
- .compile("\\[?([0-9a-zA-Z\\-_\\.:]*)\\]?:([0-9]+)(?:@[0-9]+(?:,([^,].*))?)?");
private static final Map flagLookupMap;
static {
@@ -567,32 +572,88 @@ enum ClusterNodesConverter implements Converter {
static final int LINK_STATE_INDEX = 7;
static final int SLOTS_INDEX = 8;
+ /**
+ * Value object capturing Redis' representation of a cluster node network coordinate.
+ *
+ * @author Marcin Grzejszczak
+ * @author Mark Paluch
+ */
+ record AddressPortHostname(String address, String port, @Nullable String hostname) {
+
+ /**
+ * Parses Redis {@code CLUSTER NODES} host and port segment into {@link AddressPortHostname}.
+ */
+ static AddressPortHostname parse(String hostAndPortPart) {
+
+ String[] segments = hostAndPortPart.split(",");
+ int portSeparator = segments[0].lastIndexOf(":");
+ Assert.isTrue(portSeparator != -1, "ClusterNode information does not define host and port");
+
+ String addressPart = getAddressPart(segments[0].substring(0, portSeparator));
+ String portPart = getPortPart(segments[0].substring(portSeparator + 1));
+ String hostnamePart = segments.length > 1 ? segments[1] : null;
+
+ return new AddressPortHostname(addressPart, portPart, hostnamePart);
+ }
+
+ private static String getAddressPart(String address) {
+ return address.startsWith("[") && address.endsWith("]") ? address.substring(1, address.length() - 1) : address;
+ }
+
+ private static String getPortPart(String segment) {
+
+ if (segment.contains("@")) {
+ return segment.substring(0, segment.indexOf('@'));
+ }
+
+ if (segment.contains(":")) {
+ return segment.substring(0, segment.indexOf(':'));
+ }
+
+ return segment;
+ }
+
+ public int portAsInt() {
+ return Integer.parseInt(port());
+ }
+
+ public boolean hasHostname() {
+ return StringUtils.hasText(hostname());
+ }
+
+ public String getRequiredHostname() {
+
+ if (StringUtils.hasText(hostname())) {
+ return hostname();
+ }
+
+ throw new IllegalStateException("Hostname not available");
+ }
+ }
+
@Override
public RedisClusterNode convert(String source) {
String[] args = source.split(" ");
- Matcher matcher = clusterEndpointPattern.matcher(args[HOST_PORT_INDEX]);
+ Assert.isTrue(args.length >= MASTER_ID_INDEX + 1,
+ () -> "Invalid ClusterNode information, insufficient segments: %s".formatted(source));
- Assert.isTrue(matcher.matches(), "ClusterNode information does not define host and port");
-
- String addressPart = matcher.group(1);
- String portPart = matcher.group(2);
- String hostnamePart = matcher.group(3);
+ AddressPortHostname endpoint = AddressPortHostname.parse(args[HOST_PORT_INDEX]);
SlotRange range = parseSlotRange(args);
- Set flags = parseFlags(args);
+ Set flags = parseFlags(args[FLAGS_INDEX]);
RedisClusterNodeBuilder nodeBuilder = RedisClusterNode.newRedisClusterNode()
- .listeningAt(addressPart, Integer.parseInt(portPart)) //
+ .listeningAt(endpoint.address(), endpoint.portAsInt()) //
.withId(args[ID_INDEX]) //
.promotedAs(flags.contains(Flag.MASTER) ? NodeType.MASTER : NodeType.REPLICA) //
.serving(range) //
.withFlags(flags) //
.linkState(parseLinkState(args));
- if (hostnamePart != null) {
- nodeBuilder.withName(hostnamePart);
+ if (endpoint.hasHostname()) {
+ nodeBuilder.withName(endpoint.getRequiredHostname());
}
if (!args[MASTER_ID_INDEX].isEmpty() && !args[MASTER_ID_INDEX].startsWith("-")) {
@@ -602,14 +663,12 @@ public RedisClusterNode convert(String source) {
return nodeBuilder.build();
}
- private Set parseFlags(String[] args) {
-
- String raw = args[FLAGS_INDEX];
+ private Set parseFlags(String source) {
Set flags = new LinkedHashSet<>(8, 1);
- if (StringUtils.hasText(raw)) {
- for (String flag : raw.split(",")) {
+ if (StringUtils.hasText(source)) {
+ for (String flag : source.split(",")) {
flags.add(flagLookupMap.get(flag));
}
}
diff --git a/src/main/java/org/springframework/data/redis/connection/convert/ListConverter.java b/src/main/java/org/springframework/data/redis/connection/convert/ListConverter.java
index d9b87a8c86..1a384f1af7 100644
--- a/src/main/java/org/springframework/data/redis/connection/convert/ListConverter.java
+++ b/src/main/java/org/springframework/data/redis/connection/convert/ListConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2024 the original author or authors.
+ * Copyright 2013-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/convert/LongToBooleanConverter.java b/src/main/java/org/springframework/data/redis/connection/convert/LongToBooleanConverter.java
index 0e708780bc..8686258820 100644
--- a/src/main/java/org/springframework/data/redis/connection/convert/LongToBooleanConverter.java
+++ b/src/main/java/org/springframework/data/redis/connection/convert/LongToBooleanConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2024 the original author or authors.
+ * Copyright 2013-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/convert/MapConverter.java b/src/main/java/org/springframework/data/redis/connection/convert/MapConverter.java
index 2d9bafca65..1f09154cba 100644
--- a/src/main/java/org/springframework/data/redis/connection/convert/MapConverter.java
+++ b/src/main/java/org/springframework/data/redis/connection/convert/MapConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2024 the original author or authors.
+ * Copyright 2013-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/convert/MapToPropertiesConverter.java b/src/main/java/org/springframework/data/redis/connection/convert/MapToPropertiesConverter.java
index 2e1e93e155..b25e587e0a 100644
--- a/src/main/java/org/springframework/data/redis/connection/convert/MapToPropertiesConverter.java
+++ b/src/main/java/org/springframework/data/redis/connection/convert/MapToPropertiesConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014-2024 the original author or authors.
+ * Copyright 2014-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/convert/SetConverter.java b/src/main/java/org/springframework/data/redis/connection/convert/SetConverter.java
index 244306125e..c51002e368 100644
--- a/src/main/java/org/springframework/data/redis/connection/convert/SetConverter.java
+++ b/src/main/java/org/springframework/data/redis/connection/convert/SetConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2024 the original author or authors.
+ * Copyright 2013-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/convert/StringToDataTypeConverter.java b/src/main/java/org/springframework/data/redis/connection/convert/StringToDataTypeConverter.java
index 2d1ea32cc3..39214821ca 100644
--- a/src/main/java/org/springframework/data/redis/connection/convert/StringToDataTypeConverter.java
+++ b/src/main/java/org/springframework/data/redis/connection/convert/StringToDataTypeConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2024 the original author or authors.
+ * Copyright 2013-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/convert/StringToPropertiesConverter.java b/src/main/java/org/springframework/data/redis/connection/convert/StringToPropertiesConverter.java
index 7f8889eabc..16725f049c 100644
--- a/src/main/java/org/springframework/data/redis/connection/convert/StringToPropertiesConverter.java
+++ b/src/main/java/org/springframework/data/redis/connection/convert/StringToPropertiesConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2024 the original author or authors.
+ * Copyright 2013-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/convert/StringToRedisClientInfoConverter.java b/src/main/java/org/springframework/data/redis/connection/convert/StringToRedisClientInfoConverter.java
index d2bf3aa39e..4c01ddfe6a 100644
--- a/src/main/java/org/springframework/data/redis/connection/convert/StringToRedisClientInfoConverter.java
+++ b/src/main/java/org/springframework/data/redis/connection/convert/StringToRedisClientInfoConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014-2024 the original author or authors.
+ * Copyright 2014-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/convert/TransactionResultConverter.java b/src/main/java/org/springframework/data/redis/connection/convert/TransactionResultConverter.java
index 883ceb9e15..2655245ceb 100644
--- a/src/main/java/org/springframework/data/redis/connection/convert/TransactionResultConverter.java
+++ b/src/main/java/org/springframework/data/redis/connection/convert/TransactionResultConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2024 the original author or authors.
+ * Copyright 2013-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/DefaultJedisClientConfiguration.java b/src/main/java/org/springframework/data/redis/connection/jedis/DefaultJedisClientConfiguration.java
index 50fd68211e..fee76a291f 100644
--- a/src/main/java/org/springframework/data/redis/connection/jedis/DefaultJedisClientConfiguration.java
+++ b/src/main/java/org/springframework/data/redis/connection/jedis/DefaultJedisClientConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2024 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,6 +34,7 @@
*/
class DefaultJedisClientConfiguration implements JedisClientConfiguration {
+ private final Optional customizer;
private final boolean useSsl;
private final Optional sslSocketFactory;
private final Optional sslParameters;
@@ -44,11 +45,13 @@ class DefaultJedisClientConfiguration implements JedisClientConfiguration {
private final Duration readTimeout;
private final Duration connectTimeout;
- DefaultJedisClientConfiguration(boolean useSsl, @Nullable SSLSocketFactory sslSocketFactory,
+ DefaultJedisClientConfiguration(@Nullable JedisClientConfigBuilderCustomizer customizer, boolean useSsl,
+ @Nullable SSLSocketFactory sslSocketFactory,
@Nullable SSLParameters sslParameters, @Nullable HostnameVerifier hostnameVerifier, boolean usePooling,
@Nullable GenericObjectPoolConfig poolConfig, @Nullable String clientName, Duration readTimeout,
Duration connectTimeout) {
+ this.customizer = Optional.ofNullable(customizer);
this.useSsl = useSsl;
this.sslSocketFactory = Optional.ofNullable(sslSocketFactory);
this.sslParameters = Optional.ofNullable(sslParameters);
@@ -60,6 +63,11 @@ class DefaultJedisClientConfiguration implements JedisClientConfiguration {
this.connectTimeout = connectTimeout;
}
+ @Override
+ public Optional getCustomizer() {
+ return customizer;
+ }
+
@Override
public boolean isUseSsl() {
return useSsl;
diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClientConfigBuilderCustomizer.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClientConfigBuilderCustomizer.java
new file mode 100644
index 0000000000..44e8855bf8
--- /dev/null
+++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClientConfigBuilderCustomizer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.redis.connection.jedis;
+
+import redis.clients.jedis.DefaultJedisClientConfig;
+
+/**
+ * Strategy interface for customizing {@link DefaultJedisClientConfig.Builder JedisClientConfig}. Any ClientConfig will
+ * be used to call this interface implementation so you can set the protocol, client name, etc. after Spring has applies
+ * its defaults.
+ *
+ * @author Mark Paluch
+ * @since 3.4
+ * @see redis.clients.jedis.DefaultJedisClientConfig.Builder
+ */
+@FunctionalInterface
+public interface JedisClientConfigBuilderCustomizer {
+
+ /**
+ * Customize the {@link DefaultJedisClientConfig.Builder}.
+ *
+ * @param builder the builder to customize.
+ */
+ void customize(DefaultJedisClientConfig.Builder builder);
+
+}
diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClientConfiguration.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClientConfiguration.java
index 1c9628db0c..6cf9357dfa 100644
--- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClientConfiguration.java
+++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClientConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2024 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -58,6 +58,12 @@
*/
public interface JedisClientConfiguration {
+ /**
+ * @return the optional {@link JedisClientConfigBuilderCustomizer}.
+ * @since 3.4
+ */
+ Optional getCustomizer();
+
/**
* @return {@literal true} to use SSL, {@literal false} to use unencrypted connections.
*/
@@ -119,6 +125,8 @@ static JedisClientConfigurationBuilder builder() {
/**
* Creates a default {@link JedisClientConfiguration}.
*
+ * - Customizer
+ * - none
* - SSL enabled
* - no
* - Pooling enabled
@@ -142,6 +150,15 @@ static JedisClientConfiguration defaultConfiguration() {
*/
interface JedisClientConfigurationBuilder {
+ /**
+ * Configure a {@link JedisClientConfigBuilderCustomizer} to configure
+ * {@link redis.clients.jedis.JedisClientConfig}.
+ *
+ * @return {@link JedisClientConfigurationBuilder}.
+ * @since 3.4
+ */
+ JedisClientConfigurationBuilder customize(JedisClientConfigBuilderCustomizer customizer);
+
/**
* Enable SSL connections.
*
@@ -269,6 +286,7 @@ interface JedisSslClientConfigurationBuilder {
class DefaultJedisClientConfigurationBuilder implements JedisClientConfigurationBuilder,
JedisPoolingClientConfigurationBuilder, JedisSslClientConfigurationBuilder {
+ private @Nullable JedisClientConfigBuilderCustomizer customizer;
private boolean useSsl;
private @Nullable SSLSocketFactory sslSocketFactory;
private @Nullable SSLParameters sslParameters;
@@ -281,6 +299,15 @@ class DefaultJedisClientConfigurationBuilder implements JedisClientConfiguration
private DefaultJedisClientConfigurationBuilder() {}
+ @Override
+ public JedisClientConfigurationBuilder customize(JedisClientConfigBuilderCustomizer customizer) {
+
+ Assert.notNull(customizer, "JedisClientConfigBuilderCustomizer must not be null");
+
+ this.customizer = customizer;
+ return this;
+ }
+
@Override
public JedisSslClientConfigurationBuilder useSsl() {
@@ -366,8 +393,8 @@ public JedisClientConfigurationBuilder connectTimeout(Duration connectTimeout) {
@Override
public JedisClientConfiguration build() {
- return new DefaultJedisClientConfiguration(useSsl, sslSocketFactory, sslParameters, hostnameVerifier, usePooling,
- poolConfig, clientName, readTimeout, connectTimeout);
+ return new DefaultJedisClientConfiguration(customizer, useSsl, sslSocketFactory, sslParameters, hostnameVerifier,
+ usePooling, poolConfig, clientName, readTimeout, connectTimeout);
}
}
diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClientUtils.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClientUtils.java
index af7707d9ae..ab8d1d072b 100644
--- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClientUtils.java
+++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClientUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2024 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterConnection.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterConnection.java
index 5b68de45e9..51f6f3cd14 100644
--- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterConnection.java
+++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterConnection.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2024 the original author or authors.
+ * Copyright 2015-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,8 @@
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
+import redis.clients.jedis.JedisClusterInfoCache;
+import redis.clients.jedis.Protocol;
import redis.clients.jedis.providers.ClusterConnectionProvider;
import java.time.Duration;
@@ -761,14 +763,19 @@ public Jedis getResourceForSpecificNode(RedisClusterNode node) {
return new Jedis(connection);
}
- throw new DataAccessResourceFailureException(String.format("Node %s is unknown to cluster", node));
+ throw new DataAccessResourceFailureException("Node %s is unknown to cluster".formatted(node));
}
+ @Nullable
private ConnectionPool getResourcePoolForSpecificNode(RedisClusterNode node) {
Map clusterNodes = cluster.getClusterNodes();
- if (clusterNodes.containsKey(node.asString())) {
- return clusterNodes.get(node.asString());
+ HostAndPort hap = new HostAndPort(node.getHost(),
+ node.getPort() == null ? Protocol.DEFAULT_PORT : node.getPort());
+ String key = JedisClusterInfoCache.getNodeKey(hap);
+
+ if (clusterNodes.containsKey(key)) {
+ return clusterNodes.get(key);
}
return null;
@@ -779,8 +786,8 @@ private Connection getConnectionForSpecificNode(RedisClusterNode node) {
RedisClusterNode member = topologyProvider.getTopology().lookup(node);
if (!member.hasValidHost()) {
- throw new DataAccessResourceFailureException(String
- .format("Cannot obtain connection to node %ss as it is not associated with a hostname", node.getId()));
+ throw new DataAccessResourceFailureException(
+ "Cannot obtain connection to node %ss; " + "it is not associated with a hostname".formatted(node.getId()));
}
if (member != null && connectionHandler != null) {
@@ -805,13 +812,11 @@ public void returnResourceForSpecificNode(RedisClusterNode node, Object client)
*/
public static class JedisClusterTopologyProvider implements ClusterTopologyProvider {
- private long time = 0;
+ private final JedisCluster cluster;
private final long cacheTimeMs;
- private @Nullable ClusterTopology cached;
-
- private final JedisCluster cluster;
+ private volatile @Nullable JedisClusterTopology cached;
/**
* Create new {@link JedisClusterTopologyProvider}. Uses a default cache timeout of 100 milliseconds.
@@ -842,12 +847,12 @@ public JedisClusterTopologyProvider(JedisCluster cluster, Duration cacheTimeout)
@Override
public ClusterTopology getTopology() {
- if (cached != null && shouldUseCachedValue()) {
- return cached;
+ JedisClusterTopology topology = cached;
+ if (shouldUseCachedValue(topology)) {
+ return topology;
}
Map errors = new LinkedHashMap<>();
-
List> list = new ArrayList<>(cluster.getClusterNodes().entrySet());
Collections.shuffle(list);
@@ -856,13 +861,9 @@ public ClusterTopology getTopology() {
try (Connection connection = entry.getValue().getResource()) {
- time = System.currentTimeMillis();
-
Set nodes = Converters.toSetOfRedisClusterNodes(new Jedis(connection).clusterNodes());
-
- cached = new ClusterTopology(nodes);
-
- return cached;
+ topology = cached = new JedisClusterTopology(nodes, System.currentTimeMillis(), cacheTimeMs);
+ return topology;
} catch (Exception ex) {
errors.put(entry.getKey(), ex);
@@ -872,7 +873,7 @@ public ClusterTopology getTopology() {
StringBuilder stringBuilder = new StringBuilder();
for (Entry entry : errors.entrySet()) {
- stringBuilder.append(String.format("\r\n\t- %s failed: %s", entry.getKey(), entry.getValue().getMessage()));
+ stringBuilder.append("\r\n\t- %s failed: %s".formatted(entry.getKey(), entry.getValue().getMessage()));
}
throw new ClusterStateFailureException(
@@ -887,9 +888,54 @@ public ClusterTopology getTopology() {
* topology.
* @see #JedisClusterTopologyProvider(JedisCluster, Duration)
* @since 2.2
+ * @deprecated since 3.3.4, use {@link #shouldUseCachedValue(JedisClusterTopology)} instead.
*/
+ @Deprecated(since = "3.3.4", forRemoval = true)
protected boolean shouldUseCachedValue() {
- return time + cacheTimeMs > System.currentTimeMillis();
+ return shouldUseCachedValue(cached);
+ }
+
+ /**
+ * Returns whether {@link #getTopology()} should return the cached {@link JedisClusterTopology}. Uses a time-based
+ * caching.
+ *
+ * @return {@literal true} to use the cached {@link ClusterTopology}; {@literal false} to fetch a new cluster
+ * topology.
+ * @see #JedisClusterTopologyProvider(JedisCluster, Duration)
+ * @since 3.3.4
+ */
+ protected boolean shouldUseCachedValue(@Nullable JedisClusterTopology topology) {
+ return topology != null && topology.getMaxTime() > System.currentTimeMillis();
+ }
+ }
+
+ protected static class JedisClusterTopology extends ClusterTopology {
+
+ private final long time;
+ private final long timeoutMs;
+
+ JedisClusterTopology(Set