diff --git a/src/main/java/org/springframework/data/redis/core/BoundKeyExpirationOperations.java b/src/main/java/org/springframework/data/redis/core/BoundKeyExpirationOperations.java
new file mode 100644
index 000000000..b50431b20
--- /dev/null
+++ b/src/main/java/org/springframework/data/redis/core/BoundKeyExpirationOperations.java
@@ -0,0 +1,111 @@
+/*
+ * 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.core;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.concurrent.TimeUnit;
+
+import org.springframework.data.redis.connection.ExpirationOptions;
+import org.springframework.data.redis.core.types.Expiration;
+import org.springframework.data.redis.core.types.Expirations;
+import org.springframework.lang.Nullable;
+
+/**
+ * Key Expiration operations bound to a key.
+ *
+ * @author Mark Paluch
+ * @since 3.5
+ */
+public interface BoundKeyExpirationOperations {
+
+ /**
+ * Apply {@link Expiration} to the bound key without any additional constraints.
+ *
+ * @param expiration the expiration definition.
+ * @return changes to the key. {@literal null} when used in pipeline / transaction.
+ */
+ default ExpireChanges.ExpiryChangeState expire(Expiration expiration) {
+ return expire(expiration, ExpirationOptions.none());
+ }
+
+ /**
+ * Apply {@link Expiration} to the bound key given {@link ExpirationOptions expiration options}.
+ *
+ * @param expiration the expiration definition.
+ * @param options expiration options.
+ * @return changes to the key. {@literal null} when used in pipeline / transaction.
+ */
+ @Nullable
+ ExpireChanges.ExpiryChangeState expire(Expiration expiration, ExpirationOptions options);
+
+ /**
+ * Set time to live for the bound key.
+ *
+ * @param timeout the amount of time after which the key will be expired, must not be {@literal null}.
+ * @return changes to the key. {@literal null} when used in pipeline / transaction.
+ * @throws IllegalArgumentException if the timeout is {@literal null}.
+ * @see Redis Documentation: EXPIRE
+ * @since 3.5
+ */
+ @Nullable
+ ExpireChanges.ExpiryChangeState expire(Duration timeout);
+
+ /**
+ * Set the expiration for the bound key as a {@literal date} timestamp.
+ *
+ * @param expireAt must not be {@literal null}.
+ * @return changes to the key. {@literal null} when used in pipeline / transaction.
+ * @throws IllegalArgumentException if the instant is {@literal null} or too large to represent as a {@code Date}.
+ * @see Redis Documentation: EXPIRE
+ * @since 3.5
+ */
+ @Nullable
+ ExpireChanges.ExpiryChangeState expireAt(Instant expireAt);
+
+ /**
+ * Remove the expiration from the bound key.
+ *
+ * @return changes to the key. {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: PERSIST
+ * @since 3.5
+ */
+ @Nullable
+ ExpireChanges.ExpiryChangeState persist();
+
+ /**
+ * Get the time to live for the bound key in seconds.
+ *
+ * @return the actual expirations in seconds for the key. {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: TTL
+ * @since 3.5
+ */
+ @Nullable
+ Expirations.TimeToLive getTimeToLive();
+
+ /**
+ * Get the time to live for the bound key and convert it to the given {@link TimeUnit}.
+ *
+ * @param timeUnit must not be {@literal null}.
+ * @return the actual expirations for the key in the given time unit. {@literal null} when used in pipeline /
+ * transaction.
+ * @see Redis Documentation: TTL
+ * @since 3.5
+ */
+ @Nullable
+ Expirations.TimeToLive getTimeToLive(TimeUnit timeUnit);
+
+}
diff --git a/src/main/java/org/springframework/data/redis/core/BoundKeyOperations.java b/src/main/java/org/springframework/data/redis/core/BoundKeyOperations.java
index e9d1f5e57..bb8b438a7 100644
--- a/src/main/java/org/springframework/data/redis/core/BoundKeyOperations.java
+++ b/src/main/java/org/springframework/data/redis/core/BoundKeyOperations.java
@@ -51,6 +51,16 @@ public interface BoundKeyOperations {
@Nullable
DataType getType();
+ /**
+ * Returns a bound operations object to perform expiration operations on the bound key.
+ *
+ * @return the bound operations object to perform operations on the hash field expiration.
+ * @since 3.5
+ */
+ default BoundKeyExpirationOperations expiration() {
+ return new DefaultBoundKeyExpirationOperations<>(getOperations(), getKey());
+ }
+
/**
* Returns the expiration of this key.
*
@@ -127,4 +137,10 @@ public interface BoundKeyOperations {
* @param newKey new key. Must not be {@literal null}.
*/
void rename(K newKey);
+
+ /**
+ * @return never {@literal null}.
+ */
+ RedisOperations getOperations();
+
}
diff --git a/src/main/java/org/springframework/data/redis/core/BoundOperationsProxyFactory.java b/src/main/java/org/springframework/data/redis/core/BoundOperationsProxyFactory.java
index 3492553b2..2c4bfc5f4 100644
--- a/src/main/java/org/springframework/data/redis/core/BoundOperationsProxyFactory.java
+++ b/src/main/java/org/springframework/data/redis/core/BoundOperationsProxyFactory.java
@@ -143,7 +143,7 @@ class BoundOperationsProxyFactory {
delegate.rename(invocation.getArguments()[0]);
yield null;
}
- case "getOperations" -> delegate.getOps();
+ case "getOperations" -> delegate.getOperations();
default ->
method.getDeclaringClass() == boundOperationsInterface ? doInvoke(invocation, method, operationsTarget, true)
: doInvoke(invocation, method, delegate, false);
@@ -234,12 +234,15 @@ class BoundOperationsProxyFactory {
key = newKey;
}
+ @Override
public DataType getType() {
return type;
}
- public RedisOperations