From 279d78d97b6778287b71759a97270fc187233096 Mon Sep 17 00:00:00 2001
From: Tihomir Mateev <tihomir.mateev@gmail.com>
Date: Mon, 22 Apr 2024 15:26:21 +0300
Subject: [PATCH 1/7] HEXPIRE implemented with integration tests

---
 .../core/AbstractRedisAsyncCommands.java      | 21 +++++++
 .../core/AbstractRedisReactiveCommands.java   | 21 +++++++
 .../io/lettuce/core/RedisCommandBuilder.java  | 17 ++++++
 .../core/api/async/RedisKeyAsyncCommands.java | 50 ++++++++++++++++
 .../reactive/RedisKeyReactiveCommands.java    | 51 +++++++++++++++++
 .../core/api/sync/RedisKeyCommands.java       | 50 ++++++++++++++++
 .../async/NodeSelectionKeyAsyncCommands.java  | 50 ++++++++++++++++
 .../api/sync/NodeSelectionKeyCommands.java    | 50 ++++++++++++++++
 .../io/lettuce/core/protocol/CommandType.java |  2 +-
 .../coroutines/RedisKeyCoroutinesCommands.kt  | 52 ++++++++++++++++-
 .../RedisKeyCoroutinesCommandsImpl.kt         | 18 ++++++
 .../io/lettuce/core/api/RedisKeyCommands.java | 50 ++++++++++++++++
 .../commands/KeyCommandIntegrationTests.java  | 57 +++++++++++++++++--
 13 files changed, 481 insertions(+), 8 deletions(-)

diff --git a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
index f2c6c625ce..03d0e69f28 100644
--- a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
@@ -794,6 +794,27 @@ public RedisFuture<Boolean> expire(K key, Duration seconds, ExpireArgs expireArg
         return expire(key, seconds.toMillis() / 1000, expireArgs);
     }
 
+    @Override
+    public RedisFuture<Boolean> hexpire(K key, long seconds, List<V> fields) {
+        return hexpire(key, seconds, null, fields);
+    }
+
+    @Override
+    public RedisFuture<Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, List<V> fields) {
+        return dispatch(commandBuilder.hexpire(key, seconds, expireArgs, fields));
+    }
+
+    @Override
+    public RedisFuture<Boolean> hexpire(K key, Duration seconds, List<V> fields) {
+        return hexpire(key, seconds, null, fields);
+    }
+
+    @Override
+    public RedisFuture<Boolean> hexpire(K key, Duration seconds, ExpireArgs expireArgs, List<V> fields) {
+        LettuceAssert.notNull(seconds, "Timeout must not be null");
+        return hexpire(key, seconds.toMillis() / 1000, expireArgs, fields);
+    }
+
     @Override
     public RedisFuture<Boolean> expireat(K key, long timestamp) {
         return expireat(key, timestamp, null);
diff --git a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
index 47b473495e..567401bc97 100644
--- a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
+++ b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
@@ -854,6 +854,27 @@ public Mono<Boolean> expire(K key, Duration seconds, ExpireArgs expireArgs) {
         return expire(key, seconds.toMillis() / 1000, expireArgs);
     }
 
+    @Override
+    public Mono<Boolean> hexpire(K key, long seconds, List<V> fields) {
+        return hexpire(key, seconds, null, fields);
+    }
+
+    @Override
+    public Mono<Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, List<V> fields) {
+        return createMono(() -> commandBuilder.hexpire(key, seconds, expireArgs, fields));
+    }
+
+    @Override
+    public Mono<Boolean> hexpire(K key, Duration seconds, List<V> fields) {
+        return hexpire(key, seconds, null, fields);
+    }
+
+    @Override
+    public Mono<Boolean> hexpire(K key, Duration seconds, ExpireArgs expireArgs, List<V> fields) {
+        LettuceAssert.notNull(seconds, "Timeout must not be null");
+        return hexpire(key, seconds.toMillis() / 1000, expireArgs, fields);
+    }
+
     @Override
     public Mono<Boolean> expireat(K key, long timestamp) {
         return expireat(key, timestamp, null);
diff --git a/src/main/java/io/lettuce/core/RedisCommandBuilder.java b/src/main/java/io/lettuce/core/RedisCommandBuilder.java
index 3d3d2be0be..9b82a026e8 100644
--- a/src/main/java/io/lettuce/core/RedisCommandBuilder.java
+++ b/src/main/java/io/lettuce/core/RedisCommandBuilder.java
@@ -978,6 +978,23 @@ Command<K, V, Boolean> expire(K key, long seconds, ExpireArgs expireArgs) {
         return createCommand(EXPIRE, new BooleanOutput<>(codec), args);
     }
 
+    Command<K, V, Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, List<V> fields) {
+        notNullKey(key);
+        notEmpty(fields == null ? new Object[]{} : fields.toArray());
+
+        CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key).add(seconds);
+
+        if (expireArgs != null) {
+            expireArgs.build(args);
+        }
+
+        args.add(fields.size());
+
+        fields.forEach(args::addValue);
+
+        return createCommand(HEXPIRE, new BooleanOutput<>(codec), args);
+    }
+
     Command<K, V, Boolean> expireat(K key, long timestamp, ExpireArgs expireArgs) {
         notNullKey(key);
 
diff --git a/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java
index cceffb8a71..5a6078d5b6 100644
--- a/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java
@@ -137,6 +137,56 @@ public interface RedisKeyAsyncCommands<K, V> {
      */
     RedisFuture<Boolean> expire(K key, Duration seconds, ExpireArgs expireArgs);
 
+    /**
+     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    RedisFuture<Boolean> hexpire(K key, long seconds, List<V> fields);
+
+    /**
+     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param expireArgs the expire arguments.
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    RedisFuture<Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, List<V> fields);
+
+    /**
+     * Set the time to live for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    RedisFuture<Boolean> hexpire(K key, Duration seconds, List<V> fields);
+
+    /**
+     * Set the time to live for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param expireArgs the {@link ExpireArgs}.
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    RedisFuture<Boolean> hexpire(K key, Duration seconds, ExpireArgs expireArgs, List<V> fields);
+
     /**
      * Set the expiration for a key as a UNIX timestamp.
      *
diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java
index 1fbfdda1a8..b3f1ef89f8 100644
--- a/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java
+++ b/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java
@@ -22,6 +22,7 @@
 import java.time.Duration;
 import java.time.Instant;
 import java.util.Date;
+import java.util.List;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
@@ -147,6 +148,56 @@ public interface RedisKeyReactiveCommands<K, V> {
      */
     Mono<Boolean> expire(K key, Duration seconds, ExpireArgs expireArgs);
 
+    /**
+     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Mono<Boolean> hexpire(K key, long seconds, List<V> fields);
+
+    /**
+     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param expireArgs the expire arguments.
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Mono<Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, List<V> fields);
+
+    /**
+     * Set the time to live for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Mono<Boolean> hexpire(K key, Duration seconds, List<V> fields);
+
+    /**
+     * Set the time to live for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param expireArgs the {@link ExpireArgs}.
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Mono<Boolean> hexpire(K key, Duration seconds, ExpireArgs expireArgs, List<V> fields);
+
     /**
      * Set the expiration for a key as a UNIX timestamp.
      *
diff --git a/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java
index 516e4ab072..07faab4c2e 100644
--- a/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java
+++ b/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java
@@ -146,6 +146,56 @@ public interface RedisKeyCommands<K, V> {
      */
     Boolean expire(K key, Duration seconds, ExpireArgs expireArgs);
 
+    /**
+     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Boolean hexpire(K key, long seconds, List<V> fields);
+
+    /**
+     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param expireArgs the expire arguments.
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Boolean hexpire(K key, long seconds, ExpireArgs expireArgs, List<V> fields);
+
+    /**
+     * Set the time to live for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Boolean hexpire(K key, Duration seconds, List<V> fields);
+
+    /**
+     * Set the time to live for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param expireArgs the {@link ExpireArgs}.
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Boolean hexpire(K key, Duration seconds, ExpireArgs expireArgs, List<V> fields);
+
     /**
      * Set the expiration for a key as a UNIX timestamp.
      *
diff --git a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java
index 44bbd168a3..422111c0ea 100644
--- a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java
@@ -146,6 +146,56 @@ public interface NodeSelectionKeyAsyncCommands<K, V> {
      */
     AsyncExecutions<Boolean> expire(K key, Duration seconds, ExpireArgs expireArgs);
 
+    /**
+     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    AsyncExecutions<Boolean> hexpire(K key, long seconds, List<V> fields);
+
+    /**
+     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param expireArgs the expire arguments.
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    AsyncExecutions<Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, List<V> fields);
+
+    /**
+     * Set the time to live for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    AsyncExecutions<Boolean> hexpire(K key, Duration seconds, List<V> fields);
+
+    /**
+     * Set the time to live for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param expireArgs the {@link ExpireArgs}.
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    AsyncExecutions<Boolean> hexpire(K key, Duration seconds, ExpireArgs expireArgs, List<V> fields);
+
     /**
      * Set the expiration for a key as a UNIX timestamp.
      *
diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java
index d09dd4f39a..b523434d24 100644
--- a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java
+++ b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java
@@ -146,6 +146,56 @@ public interface NodeSelectionKeyCommands<K, V> {
      */
     Executions<Boolean> expire(K key, Duration seconds, ExpireArgs expireArgs);
 
+    /**
+     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Executions<Boolean> hexpire(K key, long seconds, List<V> fields);
+
+    /**
+     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param expireArgs the expire arguments.
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Executions<Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, List<V> fields);
+
+    /**
+     * Set the time to live for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Executions<Boolean> hexpire(K key, Duration seconds, List<V> fields);
+
+    /**
+     * Set the time to live for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param expireArgs the {@link ExpireArgs}.
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Executions<Boolean> hexpire(K key, Duration seconds, ExpireArgs expireArgs, List<V> fields);
+
     /**
      * Set the expiration for a key as a UNIX timestamp.
      *
diff --git a/src/main/java/io/lettuce/core/protocol/CommandType.java b/src/main/java/io/lettuce/core/protocol/CommandType.java
index 7a9d87524d..bd4c04c982 100644
--- a/src/main/java/io/lettuce/core/protocol/CommandType.java
+++ b/src/main/java/io/lettuce/core/protocol/CommandType.java
@@ -46,7 +46,7 @@ public enum CommandType implements ProtocolKeyword {
 
     // Keys
 
-    COPY, DEL, DUMP, EXISTS, EXPIRE, EXPIREAT, EXPIRETIME, KEYS, MIGRATE, MOVE, OBJECT, PERSIST, PEXPIRE, PEXPIREAT, PEXPIRETIME, PTTL, RANDOMKEY, RENAME, RENAMENX, RESTORE, TOUCH, TTL, TYPE, SCAN, UNLINK,
+    COPY, DEL, DUMP, EXISTS, HEXPIRE, EXPIRE, EXPIREAT, EXPIRETIME, KEYS, MIGRATE, MOVE, OBJECT, PERSIST, PEXPIRE, PEXPIREAT, PEXPIRETIME, PTTL, RANDOMKEY, RENAME, RENAMENX, RESTORE, TOUCH, TTL, TYPE, SCAN, UNLINK,
 
     // String
 
diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
index 6d3f92bcad..f979c8bc4e 100644
--- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
+++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020-Present, Redis Ltd. and Contributors
+ * Copyright 2017-Present, Redis Ltd. and Contributors
  * All rights reserved.
  *
  * Licensed under the MIT License.
@@ -136,6 +136,56 @@ interface RedisKeyCoroutinesCommands<K : Any, V : Any> {
      */
     suspend fun expire(key: K, seconds: Duration, expireArgs: ExpireArgs): Boolean?
 
+    /**
+     * Set the time to live (in seconds) for a [List] of fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param fields a [List] of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: `true` if the timeout was set. `false` if `key` does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    suspend fun hexpire(key: K, seconds: Long, fields: List<V>): Boolean?
+
+    /**
+     * Set the time to live (in seconds) for a [List] of fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param expireArgs the expire arguments.
+     * @param fields a [List] of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: `true` if the timeout was set. `false` if `key` does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    suspend fun hexpire(key: K, seconds: Long, expireArgs: ExpireArgs, fields: List<V>): Boolean?
+
+    /**
+     * Set the time to live for a [List] of fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL [Duration]
+     * @param fields a [List] of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: `true` if the timeout was set. `false` if `key` does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    suspend fun hexpire(key: K, seconds: Duration, fields: List<V>): Boolean?
+
+    /**
+     * Set the time to live for a [List] of fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL [Duration]
+     * @param expireArgs the [ExpireArgs].
+     * @param fields a [List] of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: `true` if the timeout was set. `false` if `key` does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    suspend fun hexpire(key: K, seconds: Duration, expireArgs: ExpireArgs, fields: List<V>): Boolean?
+
     /**
      * Set the expiration for a key as a UNIX timestamp.
      *
diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt
index 5b62a92d3c..d088d03ad8 100644
--- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt
+++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt
@@ -74,6 +74,24 @@ internal class RedisKeyCoroutinesCommandsImpl<K : Any, V : Any>(internal val ops
     ): Boolean? =
         ops.expire(key, seconds, expireArgs).awaitFirstOrNull()
 
+    override suspend fun hexpire(key: K, seconds: Long, fields: List<V>): Boolean? =
+        ops.hexpire(key, seconds, fields).awaitFirstOrNull()
+
+    override suspend fun hexpire(key: K, seconds: Long, expireArgs: ExpireArgs, fields: List<V>): Boolean? =
+        ops.hexpire(key, seconds, expireArgs, fields).awaitFirstOrNull()
+
+
+    override suspend fun hexpire(key: K, seconds: Duration, fields: List<V>): Boolean? =
+        ops.hexpire(key, seconds, fields).awaitFirstOrNull()
+
+    override suspend fun hexpire(
+        key: K,
+        seconds: Duration,
+        expireArgs: ExpireArgs,
+        fields: List<V>
+    ): Boolean? =
+        ops.hexpire(key, seconds, expireArgs, fields).awaitFirstOrNull()
+
     override suspend fun expireat(key: K, timestamp: Date): Boolean? =
         ops.expireat(key, timestamp).awaitFirstOrNull()
 
diff --git a/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java b/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java
index bdd3568991..6f8ba3c27b 100644
--- a/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java
+++ b/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java
@@ -138,6 +138,56 @@ public interface RedisKeyCommands<K, V> {
      */
     Boolean expire(K key, Duration seconds, ExpireArgs expireArgs);
 
+    /**
+     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Boolean hexpire(K key, long seconds, List<V> fields);
+
+    /**
+     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param expireArgs the expire arguments.
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Boolean hexpire(K key, long seconds, ExpireArgs expireArgs, List<V> fields);
+
+    /**
+     * Set the time to live for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Boolean hexpire(K key, Duration seconds, List<V> fields);
+
+    /**
+     * Set the time to live for a {@link List} of fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param expireArgs the {@link ExpireArgs}.
+     * @param fields a {@link List} of fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Boolean hexpire(K key, Duration seconds, ExpireArgs expireArgs, List<V> fields);
+
     /**
      * Set the expiration for a key as a UNIX timestamp.
      *
diff --git a/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
index 45d7e3840d..fb12d44db5 100644
--- a/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
@@ -20,15 +20,11 @@
 package io.lettuce.core.commands;
 
 import static org.assertj.core.api.Assertions.*;
+import static org.awaitility.Awaitility.await;
 
 import java.time.Duration;
 import java.time.Instant;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 import javax.inject.Inject;
 
@@ -62,6 +58,12 @@
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
 public class KeyCommandIntegrationTests extends TestSupport {
 
+    public static final String MY_KEY = "hKey";
+
+    public static final String MY_FIELD = "hField";
+
+    public static final String MY_VALUE = "hValue";
+
     private final RedisCommands<String, String> redis;
 
     @Inject
@@ -192,6 +194,49 @@ void expiretime() {
         assertThat(redis.expiretime(key)).isEqualTo(expiration.getTime() / 1000);
     }
 
+    @Test
+    @EnabledOnCommand("HEXPIRE")
+    void hexpire() {
+        assertThat(redis.hset(MY_KEY, MY_FIELD, MY_VALUE)).isTrue();
+        // the below settings are required until the solution is able to support listpack entries
+        // see TODOs in https://github.com/redis/redis/pull/13172 for more details
+        assertThat(redis.configSet("hash-max-listpack-entries","0")).isEqualTo("OK");
+        assertThat(redis.configSet("set-max-listpack-value","0")).isEqualTo("OK");
+
+        assertThat(redis.hexpire("myKey", 1, Collections.singletonList("myField"))).isTrue();
+
+        await().until(() -> redis.hget("myKey", "myField") == null);
+    }
+
+    @Test
+    @EnabledOnCommand("HEXPIRE")
+    void hexpireExpireArgs() {
+        assertThat(redis.hset(MY_KEY, MY_FIELD, MY_VALUE)).isTrue();
+        // the below settings are required until the solution is able to support listpack entries
+        // see TODOs in https://github.com/redis/redis/pull/13172 for more details
+        assertThat(redis.configSet("hash-max-listpack-entries","0")).isEqualTo("OK");
+        assertThat(redis.configSet("set-max-listpack-value","0")).isEqualTo("OK");
+
+        assertThat(redis.hexpire("myKey",
+                Duration.ofSeconds(1),
+                ExpireArgs.Builder.nx(),
+                Collections.singletonList("myField"))).isTrue();
+        assertThat(redis.hexpire("myKey",
+                Duration.ofSeconds(1),
+                ExpireArgs.Builder.xx(),
+                Collections.singletonList("myField"))).isTrue();
+        assertThat(redis.hexpire("myKey",
+                Duration.ofSeconds(10),
+                ExpireArgs.Builder.gt(),
+                Collections.singletonList("myField"))).isTrue();
+        assertThat(redis.hexpire("myKey",
+                Duration.ofSeconds(1),
+                ExpireArgs.Builder.lt(),
+                Collections.singletonList("myField"))).isTrue();
+
+        await().until(() -> redis.hget("myKey", "myField") == null);
+    }
+
     @Test
     void keys() {
         assertThat(redis.keys("*")).isEqualTo(list());

From 0e867eb09a6b5bdec2fd75739badc1d8370b21d5 Mon Sep 17 00:00:00 2001
From: Tihomir Mateev <tihomir.mateev@gmail.com>
Date: Mon, 22 Apr 2024 15:59:05 +0300
Subject: [PATCH 2/7] Polishing to integration test, added unit test

---
 .../core/RedisCommandBuilderUnitTests.java    | 15 +++++++++++++
 .../commands/KeyCommandIntegrationTests.java  | 22 +++++++++----------
 2 files changed, 26 insertions(+), 11 deletions(-)

diff --git a/src/test/java/io/lettuce/core/RedisCommandBuilderUnitTests.java b/src/test/java/io/lettuce/core/RedisCommandBuilderUnitTests.java
index 9d2b676aae..2136ff0a92 100644
--- a/src/test/java/io/lettuce/core/RedisCommandBuilderUnitTests.java
+++ b/src/test/java/io/lettuce/core/RedisCommandBuilderUnitTests.java
@@ -3,7 +3,10 @@
 import static org.assertj.core.api.Assertions.*;
 
 import java.nio.charset.StandardCharsets;
+import java.util.Collections;
 
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.EmptyByteBuf;
 import org.junit.jupiter.api.Test;
 
 import io.lettuce.core.codec.StringCodec;
@@ -16,6 +19,8 @@
  * @author Mark Paluch
  */
 class RedisCommandBuilderUnitTests {
+    public static final String MY_KEY = "hKey";
+    public static final String MY_FIELD = "hField";
 
     RedisCommandBuilder<String, String> sut = new RedisCommandBuilder<>(StringCodec.UTF8);
 
@@ -29,4 +34,14 @@ void shouldCorrectlyConstructXreadgroup() {
                 .isEqualTo("stream");
     }
 
+    @Test
+    void shouldCorrectlyConstructHexpire() {
+
+        Command<String, String, ?> command = sut.hexpire(MY_KEY, 1, ExpireArgs.Builder.nx(), Collections.singletonList(MY_FIELD));
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*6\r\n" + "$7\r\n" + "HEXPIRE\r\n" + "$4\r\n" + "hKey\r\n"
+                + "$1\r\n" + "1\r\n" + "$2\r\n" + "NX\r\n" + "$1\r\n" + "1\r\n" + "$6\r\n" + "hField\r\n");
+    }
 }
diff --git a/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
index fb12d44db5..6a67e9706e 100644
--- a/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
@@ -203,9 +203,9 @@ void hexpire() {
         assertThat(redis.configSet("hash-max-listpack-entries","0")).isEqualTo("OK");
         assertThat(redis.configSet("set-max-listpack-value","0")).isEqualTo("OK");
 
-        assertThat(redis.hexpire("myKey", 1, Collections.singletonList("myField"))).isTrue();
+        assertThat(redis.hexpire(MY_KEY, 1, Collections.singletonList(MY_FIELD))).isTrue();
 
-        await().until(() -> redis.hget("myKey", "myField") == null);
+        await().until(() -> redis.hget(MY_KEY, MY_FIELD) == null);
     }
 
     @Test
@@ -217,24 +217,24 @@ void hexpireExpireArgs() {
         assertThat(redis.configSet("hash-max-listpack-entries","0")).isEqualTo("OK");
         assertThat(redis.configSet("set-max-listpack-value","0")).isEqualTo("OK");
 
-        assertThat(redis.hexpire("myKey",
+        assertThat(redis.hexpire(MY_KEY,
                 Duration.ofSeconds(1),
                 ExpireArgs.Builder.nx(),
-                Collections.singletonList("myField"))).isTrue();
-        assertThat(redis.hexpire("myKey",
+                Collections.singletonList(MY_FIELD))).isTrue();
+        assertThat(redis.hexpire(MY_KEY,
                 Duration.ofSeconds(1),
                 ExpireArgs.Builder.xx(),
-                Collections.singletonList("myField"))).isTrue();
-        assertThat(redis.hexpire("myKey",
+                Collections.singletonList(MY_FIELD))).isTrue();
+        assertThat(redis.hexpire(MY_KEY,
                 Duration.ofSeconds(10),
                 ExpireArgs.Builder.gt(),
-                Collections.singletonList("myField"))).isTrue();
-        assertThat(redis.hexpire("myKey",
+                Collections.singletonList(MY_FIELD))).isTrue();
+        assertThat(redis.hexpire(MY_KEY,
                 Duration.ofSeconds(1),
                 ExpireArgs.Builder.lt(),
-                Collections.singletonList("myField"))).isTrue();
+                Collections.singletonList(MY_FIELD))).isTrue();
 
-        await().until(() -> redis.hget("myKey", "myField") == null);
+        await().until(() -> redis.hget(MY_KEY, MY_FIELD) == null);
     }
 
     @Test

From 31c1787fcfd0178503a5f36fa00f1cb4323a8a6b Mon Sep 17 00:00:00 2001
From: Tihomir Mateev <tihomir.mateev@gmail.com>
Date: Tue, 23 Apr 2024 21:50:57 +0300
Subject: [PATCH 3/7] Move new commands to RedisHashCommands, added HEXPIREAT,
 HEXPIRETIME and HPERSIST

---
 .../core/AbstractRedisAsyncCommands.java      |  87 +++++--
 .../core/AbstractRedisReactiveCommands.java   |  88 +++++--
 .../io/lettuce/core/RedisCommandBuilder.java  | 221 ++++++++++++++++--
 .../api/async/RedisHashAsyncCommands.java     | 158 ++++++++++++-
 .../core/api/async/RedisKeyAsyncCommands.java |  68 ++----
 .../reactive/RedisHashReactiveCommands.java   | 160 ++++++++++++-
 .../reactive/RedisKeyReactiveCommands.java    |  51 ----
 .../core/api/sync/RedisHashCommands.java      | 158 ++++++++++++-
 .../core/api/sync/RedisKeyCommands.java       |  50 ----
 .../async/NodeSelectionHashAsyncCommands.java | 158 ++++++++++++-
 .../async/NodeSelectionKeyAsyncCommands.java  |  50 ----
 .../api/sync/NodeSelectionHashCommands.java   | 158 ++++++++++++-
 .../api/sync/NodeSelectionKeyCommands.java    |  60 +----
 .../io/lettuce/core/protocol/CommandType.java |   2 +-
 .../coroutines/RedisHashCoroutinesCommands.kt | 159 ++++++++++++-
 .../RedisHashCoroutinesCommandsImpl.kt        |  58 +++++
 .../coroutines/RedisKeyCoroutinesCommands.kt  |  62 +----
 .../RedisKeyCoroutinesCommandsImpl.kt         |  18 --
 .../lettuce/core/api/RedisHashCommands.java   | 147 ++++++++++++
 .../io/lettuce/core/api/RedisKeyCommands.java |  50 ----
 .../core/RedisCommandBuilderUnitTests.java    |  65 ++++--
 .../commands/HashCommandIntegrationTests.java | 117 ++++++++--
 .../commands/KeyCommandIntegrationTests.java  |  80 ++-----
 23 files changed, 1686 insertions(+), 539 deletions(-)

diff --git a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
index 03d0e69f28..10331085fb 100644
--- a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
@@ -19,18 +19,21 @@
  */
 package io.lettuce.core;
 
-import static io.lettuce.core.protocol.CommandType.*;
-
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
 import io.lettuce.core.GeoArgs.Unit;
 import io.lettuce.core.api.StatefulConnection;
-import io.lettuce.core.api.async.*;
+import io.lettuce.core.api.async.BaseRedisAsyncCommands;
+import io.lettuce.core.api.async.RedisAclAsyncCommands;
+import io.lettuce.core.api.async.RedisGeoAsyncCommands;
+import io.lettuce.core.api.async.RedisHLLAsyncCommands;
+import io.lettuce.core.api.async.RedisHashAsyncCommands;
+import io.lettuce.core.api.async.RedisKeyAsyncCommands;
+import io.lettuce.core.api.async.RedisListAsyncCommands;
+import io.lettuce.core.api.async.RedisScriptingAsyncCommands;
+import io.lettuce.core.api.async.RedisServerAsyncCommands;
+import io.lettuce.core.api.async.RedisSetAsyncCommands;
+import io.lettuce.core.api.async.RedisSortedSetAsyncCommands;
+import io.lettuce.core.api.async.RedisStringAsyncCommands;
+import io.lettuce.core.api.async.RedisTransactionalAsyncCommands;
 import io.lettuce.core.cluster.api.async.RedisClusterAsyncCommands;
 import io.lettuce.core.codec.Base16;
 import io.lettuce.core.codec.RedisCodec;
@@ -50,6 +53,19 @@
 import io.lettuce.core.protocol.ProtocolKeyword;
 import io.lettuce.core.protocol.RedisCommand;
 
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static io.lettuce.core.protocol.CommandType.EXEC;
+import static io.lettuce.core.protocol.CommandType.GEORADIUS;
+import static io.lettuce.core.protocol.CommandType.GEORADIUSBYMEMBER;
+import static io.lettuce.core.protocol.CommandType.GEORADIUSBYMEMBER_RO;
+import static io.lettuce.core.protocol.CommandType.GEORADIUS_RO;
+
 /**
  * An asynchronous and thread-safe API for a Redis connection.
  *
@@ -795,22 +811,22 @@ public RedisFuture<Boolean> expire(K key, Duration seconds, ExpireArgs expireArg
     }
 
     @Override
-    public RedisFuture<Boolean> hexpire(K key, long seconds, List<V> fields) {
+    public RedisFuture<Boolean> hexpire(K key, long seconds, K... fields) {
         return hexpire(key, seconds, null, fields);
     }
 
     @Override
-    public RedisFuture<Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, List<V> fields) {
+    public RedisFuture<Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, K... fields) {
         return dispatch(commandBuilder.hexpire(key, seconds, expireArgs, fields));
     }
 
     @Override
-    public RedisFuture<Boolean> hexpire(K key, Duration seconds, List<V> fields) {
+    public RedisFuture<Boolean> hexpire(K key, Duration seconds, K... fields) {
         return hexpire(key, seconds, null, fields);
     }
 
     @Override
-    public RedisFuture<Boolean> hexpire(K key, Duration seconds, ExpireArgs expireArgs, List<V> fields) {
+    public RedisFuture<Boolean> hexpire(K key, Duration seconds, ExpireArgs expireArgs, K... fields) {
         LettuceAssert.notNull(seconds, "Timeout must not be null");
         return hexpire(key, seconds.toMillis() / 1000, expireArgs, fields);
     }
@@ -847,11 +863,49 @@ public RedisFuture<Boolean> expireat(K key, Instant timestamp, ExpireArgs expire
         return expireat(key, timestamp.toEpochMilli() / 1000, expireArgs);
     }
 
+    @Override
+    public RedisFuture<Boolean> hexpireat(K key, long timestamp, K... fields) {
+        return hexpireat(key, timestamp, null, fields);
+    }
+
+    @Override
+    public RedisFuture<Boolean> hexpireat(K key, long timestamp, ExpireArgs expireArgs, K... fields) {
+        return dispatch(commandBuilder.hexpireat(key, timestamp, expireArgs, fields));
+
+    }
+
+    @Override
+    public RedisFuture<Boolean> hexpireat(K key, Date timestamp, K... fields) {
+        return hexpireat(key, timestamp, null, fields);
+    }
+
+    @Override
+    public RedisFuture<Boolean> hexpireat(K key, Date timestamp, ExpireArgs expireArgs, K... fields) {
+        LettuceAssert.notNull(timestamp, "Timestamp must not be null");
+        return hexpireat(key, timestamp.getTime() / 1000, expireArgs, fields);
+    }
+
+    @Override
+    public RedisFuture<Boolean> hexpireat(K key, Instant timestamp, K... fields) {
+        return hexpireat(key, timestamp, null, fields);
+    }
+
+    @Override
+    public RedisFuture<Boolean> hexpireat(K key, Instant timestamp, ExpireArgs expireArgs, K... fields) {
+        LettuceAssert.notNull(timestamp, "Timestamp must not be null");
+        return hexpireat(key, timestamp.toEpochMilli() / 1000, expireArgs, fields);
+    }
+
     @Override
     public RedisFuture<Long> expiretime(K key) {
         return dispatch(commandBuilder.expiretime(key));
     }
 
+    @Override
+    public RedisFuture<Long> hexpiretime(K key, K... fields) {
+        return dispatch(commandBuilder.hexpiretime(key, fields));
+    }
+
     @Override
     public <T> RedisFuture<T> fcall(String function, ScriptOutputType type, K... keys) {
         return dispatch(commandBuilder.fcall(function, type, false, keys));
@@ -1510,6 +1564,11 @@ public RedisFuture<Boolean> persist(K key) {
         return dispatch(commandBuilder.persist(key));
     }
 
+    @Override
+    public RedisFuture<Boolean> hpersist(K key, K... fields) {
+        return dispatch(commandBuilder.hpersist(key, fields));
+    }
+
     @Override
     public RedisFuture<Boolean> pexpire(K key, long milliseconds) {
         return pexpire(key, milliseconds, null);
diff --git a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
index 567401bc97..42efa41acf 100644
--- a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
+++ b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
@@ -19,19 +19,21 @@
  */
 package io.lettuce.core;
 
-import static io.lettuce.core.protocol.CommandType.*;
-
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Supplier;
-
 import io.lettuce.core.GeoArgs.Unit;
 import io.lettuce.core.api.StatefulConnection;
-import io.lettuce.core.api.reactive.*;
+import io.lettuce.core.api.reactive.BaseRedisReactiveCommands;
+import io.lettuce.core.api.reactive.RedisAclReactiveCommands;
+import io.lettuce.core.api.reactive.RedisGeoReactiveCommands;
+import io.lettuce.core.api.reactive.RedisHLLReactiveCommands;
+import io.lettuce.core.api.reactive.RedisHashReactiveCommands;
+import io.lettuce.core.api.reactive.RedisKeyReactiveCommands;
+import io.lettuce.core.api.reactive.RedisListReactiveCommands;
+import io.lettuce.core.api.reactive.RedisScriptingReactiveCommands;
+import io.lettuce.core.api.reactive.RedisServerReactiveCommands;
+import io.lettuce.core.api.reactive.RedisSetReactiveCommands;
+import io.lettuce.core.api.reactive.RedisSortedSetReactiveCommands;
+import io.lettuce.core.api.reactive.RedisStringReactiveCommands;
+import io.lettuce.core.api.reactive.RedisTransactionalReactiveCommands;
 import io.lettuce.core.cluster.api.reactive.RedisClusterReactiveCommands;
 import io.lettuce.core.codec.Base16;
 import io.lettuce.core.codec.RedisCodec;
@@ -59,6 +61,20 @@
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import static io.lettuce.core.protocol.CommandType.EXEC;
+import static io.lettuce.core.protocol.CommandType.GEORADIUS;
+import static io.lettuce.core.protocol.CommandType.GEORADIUSBYMEMBER;
+import static io.lettuce.core.protocol.CommandType.GEORADIUSBYMEMBER_RO;
+import static io.lettuce.core.protocol.CommandType.GEORADIUS_RO;
+
 /**
  * A reactive and thread-safe API for a Redis connection.
  *
@@ -855,22 +871,22 @@ public Mono<Boolean> expire(K key, Duration seconds, ExpireArgs expireArgs) {
     }
 
     @Override
-    public Mono<Boolean> hexpire(K key, long seconds, List<V> fields) {
+    public Mono<Boolean> hexpire(K key, long seconds, K... fields) {
         return hexpire(key, seconds, null, fields);
     }
 
     @Override
-    public Mono<Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, List<V> fields) {
+    public Mono<Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, K... fields) {
         return createMono(() -> commandBuilder.hexpire(key, seconds, expireArgs, fields));
     }
 
     @Override
-    public Mono<Boolean> hexpire(K key, Duration seconds, List<V> fields) {
+    public Mono<Boolean> hexpire(K key, Duration seconds, K... fields) {
         return hexpire(key, seconds, null, fields);
     }
 
     @Override
-    public Mono<Boolean> hexpire(K key, Duration seconds, ExpireArgs expireArgs, List<V> fields) {
+    public Mono<Boolean> hexpire(K key, Duration seconds, ExpireArgs expireArgs, K... fields) {
         LettuceAssert.notNull(seconds, "Timeout must not be null");
         return hexpire(key, seconds.toMillis() / 1000, expireArgs, fields);
     }
@@ -907,11 +923,48 @@ public Mono<Boolean> expireat(K key, Instant timestamp, ExpireArgs expireArgs) {
         return expireat(key, timestamp.toEpochMilli() / 1000, expireArgs);
     }
 
+    @Override
+    public Mono<Boolean> hexpireat(K key, long timestamp, K... fields) {
+        return hexpireat(key, timestamp, null, fields);
+    }
+
+    @Override
+    public Mono<Boolean> hexpireat(K key, long timestamp, ExpireArgs expireArgs, K... fields) {
+        return createMono(() -> commandBuilder.hexpireat(key, timestamp, expireArgs, fields));
+    }
+
+    @Override
+    public Mono<Boolean> hexpireat(K key, Date timestamp, K... fields) {
+        return hexpireat(key, timestamp, null, fields);
+    }
+
+    @Override
+    public Mono<Boolean> hexpireat(K key, Date timestamp, ExpireArgs expireArgs, K... fields) {
+        LettuceAssert.notNull(timestamp, "Timestamp must not be null");
+        return hexpireat(key, timestamp.getTime() / 1000, expireArgs, fields);
+    }
+
+    @Override
+    public Mono<Boolean> hexpireat(K key, Instant timestamp, K... fields) {
+        return hexpireat(key, timestamp, null, fields);
+    }
+
+    @Override
+    public Mono<Boolean> hexpireat(K key, Instant timestamp, ExpireArgs expireArgs, K... fields) {
+        LettuceAssert.notNull(timestamp, "Timestamp must not be null");
+        return hexpireat(key, timestamp.toEpochMilli() / 1000, expireArgs, fields);
+    }
+
     @Override
     public Mono<Long> expiretime(K key) {
         return createMono(() -> commandBuilder.expiretime(key));
     }
 
+    @Override
+    public Mono<Long> hexpiretime(K key, K... fields) {
+        return createMono(() -> commandBuilder.hexpiretime(key, fields));
+    }
+
     @Override
     public <T> Flux<T> fcall(String function, ScriptOutputType type, K... keys) {
         return createFlux(() -> commandBuilder.fcall(function, type, false, keys));
@@ -1577,6 +1630,11 @@ public Mono<Boolean> persist(K key) {
         return createMono(() -> commandBuilder.persist(key));
     }
 
+    @Override
+    public Mono<Boolean> hpersist(K key, K... fields) {
+        return createMono(() -> commandBuilder.hpersist(key, fields));
+    }
+
     @Override
     public Mono<Boolean> pexpire(K key, long milliseconds) {
         return pexpire(key, milliseconds, null);
diff --git a/src/main/java/io/lettuce/core/RedisCommandBuilder.java b/src/main/java/io/lettuce/core/RedisCommandBuilder.java
index 9b82a026e8..48b68fe1df 100644
--- a/src/main/java/io/lettuce/core/RedisCommandBuilder.java
+++ b/src/main/java/io/lettuce/core/RedisCommandBuilder.java
@@ -19,19 +19,6 @@
  */
 package io.lettuce.core;
 
-import static io.lettuce.core.internal.LettuceStrings.*;
-import static io.lettuce.core.protocol.CommandKeyword.*;
-import static io.lettuce.core.protocol.CommandType.*;
-import static io.lettuce.core.protocol.CommandType.COPY;
-import static io.lettuce.core.protocol.CommandType.SAVE;
-
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
 import io.lettuce.core.Range.Boundary;
 import io.lettuce.core.XReadArgs.StreamOffset;
 import io.lettuce.core.codec.RedisCodec;
@@ -40,7 +27,64 @@
 import io.lettuce.core.models.stream.ClaimedMessages;
 import io.lettuce.core.models.stream.PendingMessage;
 import io.lettuce.core.models.stream.PendingMessages;
-import io.lettuce.core.output.*;
+import io.lettuce.core.output.ArrayOutput;
+import io.lettuce.core.output.BooleanListOutput;
+import io.lettuce.core.output.BooleanOutput;
+import io.lettuce.core.output.ByteArrayOutput;
+import io.lettuce.core.output.ClaimedMessagesOutput;
+import io.lettuce.core.output.CommandOutput;
+import io.lettuce.core.output.DateOutput;
+import io.lettuce.core.output.DoubleListOutput;
+import io.lettuce.core.output.DoubleOutput;
+import io.lettuce.core.output.EnumSetOutput;
+import io.lettuce.core.output.GenericMapOutput;
+import io.lettuce.core.output.GeoCoordinatesListOutput;
+import io.lettuce.core.output.GeoCoordinatesValueListOutput;
+import io.lettuce.core.output.GeoWithinListOutput;
+import io.lettuce.core.output.IntegerListOutput;
+import io.lettuce.core.output.IntegerOutput;
+import io.lettuce.core.output.KeyListOutput;
+import io.lettuce.core.output.KeyOutput;
+import io.lettuce.core.output.KeyScanOutput;
+import io.lettuce.core.output.KeyScanStreamingOutput;
+import io.lettuce.core.output.KeyStreamingChannel;
+import io.lettuce.core.output.KeyStreamingOutput;
+import io.lettuce.core.output.KeyValueListOutput;
+import io.lettuce.core.output.KeyValueListScoredValueOutput;
+import io.lettuce.core.output.KeyValueOfScoredValueOutput;
+import io.lettuce.core.output.KeyValueOutput;
+import io.lettuce.core.output.KeyValueScanStreamingOutput;
+import io.lettuce.core.output.KeyValueScoredValueOutput;
+import io.lettuce.core.output.KeyValueStreamingChannel;
+import io.lettuce.core.output.KeyValueStreamingOutput;
+import io.lettuce.core.output.KeyValueValueListOutput;
+import io.lettuce.core.output.ListOfGenericMapsOutput;
+import io.lettuce.core.output.MapOutput;
+import io.lettuce.core.output.MapScanOutput;
+import io.lettuce.core.output.NestedMultiOutput;
+import io.lettuce.core.output.ObjectOutput;
+import io.lettuce.core.output.PendingMessageListOutput;
+import io.lettuce.core.output.PendingMessagesOutput;
+import io.lettuce.core.output.ScoredValueListOutput;
+import io.lettuce.core.output.ScoredValueOutput;
+import io.lettuce.core.output.ScoredValueScanOutput;
+import io.lettuce.core.output.ScoredValueScanStreamingOutput;
+import io.lettuce.core.output.ScoredValueStreamingChannel;
+import io.lettuce.core.output.ScoredValueStreamingOutput;
+import io.lettuce.core.output.StatusOutput;
+import io.lettuce.core.output.StreamMessageListOutput;
+import io.lettuce.core.output.StreamReadOutput;
+import io.lettuce.core.output.StringListOutput;
+import io.lettuce.core.output.StringMatchResultOutput;
+import io.lettuce.core.output.StringValueListOutput;
+import io.lettuce.core.output.ValueListOutput;
+import io.lettuce.core.output.ValueOutput;
+import io.lettuce.core.output.ValueScanOutput;
+import io.lettuce.core.output.ValueScanStreamingOutput;
+import io.lettuce.core.output.ValueSetOutput;
+import io.lettuce.core.output.ValueStreamingChannel;
+import io.lettuce.core.output.ValueStreamingOutput;
+import io.lettuce.core.output.ValueValueListOutput;
 import io.lettuce.core.protocol.BaseRedisCommandBuilder;
 import io.lettuce.core.protocol.Command;
 import io.lettuce.core.protocol.CommandArgs;
@@ -48,6 +92,111 @@
 import io.lettuce.core.protocol.CommandType;
 import io.lettuce.core.protocol.RedisCommand;
 
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static io.lettuce.core.internal.LettuceStrings.string;
+import static io.lettuce.core.protocol.CommandKeyword.ADDSLOTS;
+import static io.lettuce.core.protocol.CommandKeyword.ADDSLOTSRANGE;
+import static io.lettuce.core.protocol.CommandKeyword.AFTER;
+import static io.lettuce.core.protocol.CommandKeyword.AND;
+import static io.lettuce.core.protocol.CommandKeyword.BEFORE;
+import static io.lettuce.core.protocol.CommandKeyword.BUMPEPOCH;
+import static io.lettuce.core.protocol.CommandKeyword.BYLEX;
+import static io.lettuce.core.protocol.CommandKeyword.BYSCORE;
+import static io.lettuce.core.protocol.CommandKeyword.CACHING;
+import static io.lettuce.core.protocol.CommandKeyword.CAT;
+import static io.lettuce.core.protocol.CommandKeyword.CHANNELS;
+import static io.lettuce.core.protocol.CommandKeyword.CONSUMERS;
+import static io.lettuce.core.protocol.CommandKeyword.COUNT;
+import static io.lettuce.core.protocol.CommandKeyword.COUNTKEYSINSLOT;
+import static io.lettuce.core.protocol.CommandKeyword.CREATE;
+import static io.lettuce.core.protocol.CommandKeyword.DELSLOTS;
+import static io.lettuce.core.protocol.CommandKeyword.DELSLOTSRANGE;
+import static io.lettuce.core.protocol.CommandKeyword.DELUSER;
+import static io.lettuce.core.protocol.CommandKeyword.DRYRUN;
+import static io.lettuce.core.protocol.CommandKeyword.ENCODING;
+import static io.lettuce.core.protocol.CommandKeyword.FAILOVER;
+import static io.lettuce.core.protocol.CommandKeyword.FLUSH;
+import static io.lettuce.core.protocol.CommandKeyword.FLUSHSLOTS;
+import static io.lettuce.core.protocol.CommandKeyword.FORCE;
+import static io.lettuce.core.protocol.CommandKeyword.FORGET;
+import static io.lettuce.core.protocol.CommandKeyword.FREQ;
+import static io.lettuce.core.protocol.CommandKeyword.GENPASS;
+import static io.lettuce.core.protocol.CommandKeyword.GETKEYSINSLOT;
+import static io.lettuce.core.protocol.CommandKeyword.GETNAME;
+import static io.lettuce.core.protocol.CommandKeyword.GETREDIR;
+import static io.lettuce.core.protocol.CommandKeyword.GETUSER;
+import static io.lettuce.core.protocol.CommandKeyword.GROUPS;
+import static io.lettuce.core.protocol.CommandKeyword.HARD;
+import static io.lettuce.core.protocol.CommandKeyword.HTSTATS;
+import static io.lettuce.core.protocol.CommandKeyword.ID;
+import static io.lettuce.core.protocol.CommandKeyword.IDLETIME;
+import static io.lettuce.core.protocol.CommandKeyword.IMPORTING;
+import static io.lettuce.core.protocol.CommandKeyword.KEYSLOT;
+import static io.lettuce.core.protocol.CommandKeyword.KILL;
+import static io.lettuce.core.protocol.CommandKeyword.LEN;
+import static io.lettuce.core.protocol.CommandKeyword.LIMIT;
+import static io.lettuce.core.protocol.CommandKeyword.LIST;
+import static io.lettuce.core.protocol.CommandKeyword.LOAD;
+import static io.lettuce.core.protocol.CommandKeyword.LOG;
+import static io.lettuce.core.protocol.CommandKeyword.MAXLEN;
+import static io.lettuce.core.protocol.CommandKeyword.MEET;
+import static io.lettuce.core.protocol.CommandKeyword.MIGRATING;
+import static io.lettuce.core.protocol.CommandKeyword.NO;
+import static io.lettuce.core.protocol.CommandKeyword.NODE;
+import static io.lettuce.core.protocol.CommandKeyword.NODES;
+import static io.lettuce.core.protocol.CommandKeyword.NOSAVE;
+import static io.lettuce.core.protocol.CommandKeyword.NOT;
+import static io.lettuce.core.protocol.CommandKeyword.NOVALUES;
+import static io.lettuce.core.protocol.CommandKeyword.NUMPAT;
+import static io.lettuce.core.protocol.CommandKeyword.NUMSUB;
+import static io.lettuce.core.protocol.CommandKeyword.OFF;
+import static io.lettuce.core.protocol.CommandKeyword.ON;
+import static io.lettuce.core.protocol.CommandKeyword.ONE;
+import static io.lettuce.core.protocol.CommandKeyword.OR;
+import static io.lettuce.core.protocol.CommandKeyword.PAUSE;
+import static io.lettuce.core.protocol.CommandKeyword.REFCOUNT;
+import static io.lettuce.core.protocol.CommandKeyword.RELOAD;
+import static io.lettuce.core.protocol.CommandKeyword.REPLACE;
+import static io.lettuce.core.protocol.CommandKeyword.REPLICAS;
+import static io.lettuce.core.protocol.CommandKeyword.REPLICATE;
+import static io.lettuce.core.protocol.CommandKeyword.RESET;
+import static io.lettuce.core.protocol.CommandKeyword.RESETSTAT;
+import static io.lettuce.core.protocol.CommandKeyword.RESTART;
+import static io.lettuce.core.protocol.CommandKeyword.REV;
+import static io.lettuce.core.protocol.CommandKeyword.REWRITE;
+import static io.lettuce.core.protocol.CommandKeyword.SAVECONFIG;
+import static io.lettuce.core.protocol.CommandKeyword.SEGFAULT;
+import static io.lettuce.core.protocol.CommandKeyword.SETINFO;
+import static io.lettuce.core.protocol.CommandKeyword.SETNAME;
+import static io.lettuce.core.protocol.CommandKeyword.SETSLOT;
+import static io.lettuce.core.protocol.CommandKeyword.SETUSER;
+import static io.lettuce.core.protocol.CommandKeyword.SHARDCHANNELS;
+import static io.lettuce.core.protocol.CommandKeyword.SHARDNUMSUB;
+import static io.lettuce.core.protocol.CommandKeyword.SHARDS;
+import static io.lettuce.core.protocol.CommandKeyword.SLAVES;
+import static io.lettuce.core.protocol.CommandKeyword.SLOTS;
+import static io.lettuce.core.protocol.CommandKeyword.SOFT;
+import static io.lettuce.core.protocol.CommandKeyword.STABLE;
+import static io.lettuce.core.protocol.CommandKeyword.STREAM;
+import static io.lettuce.core.protocol.CommandKeyword.TAKEOVER;
+import static io.lettuce.core.protocol.CommandKeyword.TRACKING;
+import static io.lettuce.core.protocol.CommandKeyword.UNBLOCK;
+import static io.lettuce.core.protocol.CommandKeyword.USAGE;
+import static io.lettuce.core.protocol.CommandKeyword.USERS;
+import static io.lettuce.core.protocol.CommandKeyword.WHOAMI;
+import static io.lettuce.core.protocol.CommandKeyword.WITHSCORE;
+import static io.lettuce.core.protocol.CommandKeyword.WITHSCORES;
+import static io.lettuce.core.protocol.CommandKeyword.WITHVALUES;
+import static io.lettuce.core.protocol.CommandKeyword.XOR;
+import static io.lettuce.core.protocol.CommandKeyword.YES;
+import static io.lettuce.core.protocol.CommandType.*;
+
 /**
  * @param <K>
  * @param <V>
@@ -978,9 +1127,9 @@ Command<K, V, Boolean> expire(K key, long seconds, ExpireArgs expireArgs) {
         return createCommand(EXPIRE, new BooleanOutput<>(codec), args);
     }
 
-    Command<K, V, Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, List<V> fields) {
+    Command<K, V, Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, K... fields) {
         notNullKey(key);
-        notEmpty(fields == null ? new Object[]{} : fields.toArray());
+        notEmpty(fields);
 
         CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key).add(seconds);
 
@@ -988,13 +1137,28 @@ Command<K, V, Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, List<
             expireArgs.build(args);
         }
 
-        args.add(fields.size());
-
-        fields.forEach(args::addValue);
+        args.add(fields.length);
+        args.addKeys(fields);
 
         return createCommand(HEXPIRE, new BooleanOutput<>(codec), args);
     }
 
+    Command<K, V, Boolean> hexpireat(K key, long seconds, ExpireArgs expireArgs, K... fields) {
+        notNullKey(key);
+        notEmpty(fields);
+
+        CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key).add(seconds);
+
+        if (expireArgs != null) {
+            expireArgs.build(args);
+        }
+
+        args.add(fields.length);
+        args.addKeys(fields);
+
+        return createCommand(HEXPIREAT, new BooleanOutput<>(codec), args);
+    }
+
     Command<K, V, Boolean> expireat(K key, long timestamp, ExpireArgs expireArgs) {
         notNullKey(key);
 
@@ -1014,6 +1178,15 @@ Command<K, V, Long> expiretime(K key) {
         return createCommand(EXPIRETIME, new IntegerOutput<>(codec), args);
     }
 
+    Command<K, V, Long> hexpiretime(K key, K... fields) {
+        notNullKey(key);
+
+        CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
+        args.add(fields.length);
+        args.addKeys(fields);
+        return createCommand(HEXPIRETIME, new IntegerOutput<>(codec), args);
+    }
+
     Command<K, V, String> flushall() {
         return createCommand(FLUSHALL, new StatusOutput<>(codec));
     }
@@ -2060,6 +2233,16 @@ Command<K, V, Boolean> persist(K key) {
         return createCommand(PERSIST, new BooleanOutput<>(codec), key);
     }
 
+    Command<K, V, Boolean> hpersist(K key, K... fields) {
+        notNullKey(key);
+
+        CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
+        args.add(fields.length);
+        args.addKeys(fields);
+
+        return createCommand(HPERSIST, new BooleanOutput<>(codec), args);
+    }
+
     Command<K, V, Boolean> pexpire(K key, long milliseconds, ExpireArgs expireArgs) {
         notNullKey(key);
 
diff --git a/src/main/java/io/lettuce/core/api/async/RedisHashAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisHashAsyncCommands.java
index cc9b8b1489..9f514450fa 100644
--- a/src/main/java/io/lettuce/core/api/async/RedisHashAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/api/async/RedisHashAsyncCommands.java
@@ -19,9 +19,7 @@
  */
 package io.lettuce.core.api.async;
 
-import java.util.List;
-import java.util.Map;
-
+import io.lettuce.core.ExpireArgs;
 import io.lettuce.core.KeyScanCursor;
 import io.lettuce.core.KeyValue;
 import io.lettuce.core.MapScanCursor;
@@ -33,6 +31,12 @@
 import io.lettuce.core.output.KeyValueStreamingChannel;
 import io.lettuce.core.output.ValueStreamingChannel;
 
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
 /**
  * Asynchronous executed commands for Hashes (Key-Value pairs).
  *
@@ -429,4 +433,152 @@ public interface RedisHashAsyncCommands<K, V> {
      * @return Long count of the keys.
      */
     RedisFuture<Long> hvals(ValueStreamingChannel<V> channel, K key);
+
+    /**
+     * Set the time to live (in seconds) for one or more fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    RedisFuture<Boolean> hexpire(K key, long seconds, K... fields);
+
+    /**
+     * Set the time to live (in seconds) for one or more fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    RedisFuture<Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    RedisFuture<Boolean> hexpire(K key, Duration seconds, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param expireArgs the {@link ExpireArgs}.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    RedisFuture<Boolean> hexpire(K key, Duration seconds, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    RedisFuture<Boolean> hexpireat(K key, long timestamp, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    RedisFuture<Boolean> hexpireat(K key, long timestamp, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    RedisFuture<Boolean> hexpireat(K key, Date timestamp, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    RedisFuture<Boolean> hexpireat(K key, Date timestamp, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    RedisFuture<Boolean> hexpireat(K key, Instant timestamp, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    RedisFuture<Boolean> hexpireat(K key, Instant timestamp, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Get the time to live for one or more fields in as unix timestamp in seconds.
+     *
+     * @param key the key.
+     * @param fields one or more fields to get the TTL for.
+     * @return Long integer-reply in seconds, or a negative value in order 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 7.0
+     */
+    RedisFuture<Long> hexpiretime(K key, K... fields);
+
+    /**
+     * Remove the expiration from one or more fields.
+     *
+     * @param key the key.
+     * @param fields one or more fields to remove the TTL for.
+     * @return Boolean integer-reply specifically:
+     *
+     *         {@code true} if the timeout was removed. {@code false} if {@code key} does not exist or does not have an
+     *         associated timeout.
+     */
+    RedisFuture<Boolean> hpersist(K key, K... fields);
 }
diff --git a/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java
index 5a6078d5b6..b5253b4e9d 100644
--- a/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java
@@ -19,15 +19,25 @@
  */
 package io.lettuce.core.api.async;
 
+import io.lettuce.core.CopyArgs;
+import io.lettuce.core.ExpireArgs;
+import io.lettuce.core.KeyScanArgs;
+import io.lettuce.core.KeyScanCursor;
+import io.lettuce.core.MigrateArgs;
+import io.lettuce.core.RedisFuture;
+import io.lettuce.core.RestoreArgs;
+import io.lettuce.core.ScanArgs;
+import io.lettuce.core.ScanCursor;
+import io.lettuce.core.SortArgs;
+import io.lettuce.core.StreamScanCursor;
+import io.lettuce.core.output.KeyStreamingChannel;
+import io.lettuce.core.output.ValueStreamingChannel;
+
 import java.time.Duration;
 import java.time.Instant;
 import java.util.Date;
 import java.util.List;
 
-import io.lettuce.core.*;
-import io.lettuce.core.output.KeyStreamingChannel;
-import io.lettuce.core.output.ValueStreamingChannel;
-
 /**
  * Asynchronous executed commands for Keys (Key manipulation/querying).
  *
@@ -137,56 +147,6 @@ public interface RedisKeyAsyncCommands<K, V> {
      */
     RedisFuture<Boolean> expire(K key, Duration seconds, ExpireArgs expireArgs);
 
-    /**
-     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key of the fields.
-     * @param seconds the seconds type: long.
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    RedisFuture<Boolean> hexpire(K key, long seconds, List<V> fields);
-
-    /**
-     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key of the fields.
-     * @param seconds the seconds type: long.
-     * @param expireArgs the expire arguments.
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    RedisFuture<Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, List<V> fields);
-
-    /**
-     * Set the time to live for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key.
-     * @param seconds the TTL {@link Duration}
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    RedisFuture<Boolean> hexpire(K key, Duration seconds, List<V> fields);
-
-    /**
-     * Set the time to live for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key.
-     * @param seconds the TTL {@link Duration}
-     * @param expireArgs the {@link ExpireArgs}.
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    RedisFuture<Boolean> hexpire(K key, Duration seconds, ExpireArgs expireArgs, List<V> fields);
-
     /**
      * Set the expiration for a key as a UNIX timestamp.
      *
diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisHashReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisHashReactiveCommands.java
index 080087510a..e02fe5b80a 100644
--- a/src/main/java/io/lettuce/core/api/reactive/RedisHashReactiveCommands.java
+++ b/src/main/java/io/lettuce/core/api/reactive/RedisHashReactiveCommands.java
@@ -19,10 +19,7 @@
  */
 package io.lettuce.core.api.reactive;
 
-import java.util.Map;
-
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
+import io.lettuce.core.ExpireArgs;
 import io.lettuce.core.KeyScanCursor;
 import io.lettuce.core.KeyValue;
 import io.lettuce.core.MapScanCursor;
@@ -32,6 +29,13 @@
 import io.lettuce.core.output.KeyStreamingChannel;
 import io.lettuce.core.output.KeyValueStreamingChannel;
 import io.lettuce.core.output.ValueStreamingChannel;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.Map;
 
 /**
  * Reactive executed commands for Hashes (Key-Value pairs).
@@ -451,4 +455,152 @@ public interface RedisHashReactiveCommands<K, V> {
      */
     @Deprecated
     Mono<Long> hvals(ValueStreamingChannel<V> channel, K key);
+
+    /**
+     * Set the time to live (in seconds) for one or more fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Mono<Boolean> hexpire(K key, long seconds, K... fields);
+
+    /**
+     * Set the time to live (in seconds) for one or more fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Mono<Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Mono<Boolean> hexpire(K key, Duration seconds, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param expireArgs the {@link ExpireArgs}.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Mono<Boolean> hexpire(K key, Duration seconds, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Mono<Boolean> hexpireat(K key, long timestamp, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Mono<Boolean> hexpireat(K key, long timestamp, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Mono<Boolean> hexpireat(K key, Date timestamp, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Mono<Boolean> hexpireat(K key, Date timestamp, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Mono<Boolean> hexpireat(K key, Instant timestamp, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Mono<Boolean> hexpireat(K key, Instant timestamp, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Get the time to live for one or more fields in as unix timestamp in seconds.
+     *
+     * @param key the key.
+     * @param fields one or more fields to get the TTL for.
+     * @return Long integer-reply in seconds, or a negative value in order 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 7.0
+     */
+    Mono<Long> hexpiretime(K key, K... fields);
+
+    /**
+     * Remove the expiration from one or more fields.
+     *
+     * @param key the key.
+     * @param fields one or more fields to remove the TTL for.
+     * @return Boolean integer-reply specifically:
+     *
+     *         {@code true} if the timeout was removed. {@code false} if {@code key} does not exist or does not have an
+     *         associated timeout.
+     */
+    Mono<Boolean> hpersist(K key, K... fields);
 }
diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java
index b3f1ef89f8..1fbfdda1a8 100644
--- a/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java
+++ b/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java
@@ -22,7 +22,6 @@
 import java.time.Duration;
 import java.time.Instant;
 import java.util.Date;
-import java.util.List;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
@@ -148,56 +147,6 @@ public interface RedisKeyReactiveCommands<K, V> {
      */
     Mono<Boolean> expire(K key, Duration seconds, ExpireArgs expireArgs);
 
-    /**
-     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key of the fields.
-     * @param seconds the seconds type: long.
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    Mono<Boolean> hexpire(K key, long seconds, List<V> fields);
-
-    /**
-     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key of the fields.
-     * @param seconds the seconds type: long.
-     * @param expireArgs the expire arguments.
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    Mono<Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, List<V> fields);
-
-    /**
-     * Set the time to live for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key.
-     * @param seconds the TTL {@link Duration}
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    Mono<Boolean> hexpire(K key, Duration seconds, List<V> fields);
-
-    /**
-     * Set the time to live for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key.
-     * @param seconds the TTL {@link Duration}
-     * @param expireArgs the {@link ExpireArgs}.
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    Mono<Boolean> hexpire(K key, Duration seconds, ExpireArgs expireArgs, List<V> fields);
-
     /**
      * Set the expiration for a key as a UNIX timestamp.
      *
diff --git a/src/main/java/io/lettuce/core/api/sync/RedisHashCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisHashCommands.java
index 816ec08227..ab41a1cafd 100644
--- a/src/main/java/io/lettuce/core/api/sync/RedisHashCommands.java
+++ b/src/main/java/io/lettuce/core/api/sync/RedisHashCommands.java
@@ -19,9 +19,7 @@
  */
 package io.lettuce.core.api.sync;
 
-import java.util.List;
-import java.util.Map;
-
+import io.lettuce.core.ExpireArgs;
 import io.lettuce.core.KeyScanCursor;
 import io.lettuce.core.KeyValue;
 import io.lettuce.core.MapScanCursor;
@@ -32,6 +30,12 @@
 import io.lettuce.core.output.KeyValueStreamingChannel;
 import io.lettuce.core.output.ValueStreamingChannel;
 
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
 /**
  * Synchronous executed commands for Hashes (Key-Value pairs).
  *
@@ -430,4 +434,152 @@ public interface RedisHashCommands<K, V> {
      * @return Long count of the keys.
      */
     Long hvals(ValueStreamingChannel<V> channel, K key);
+
+    /**
+     * Set the time to live (in seconds) for one or more fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Boolean hexpire(K key, long seconds, K... fields);
+
+    /**
+     * Set the time to live (in seconds) for one or more fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Boolean hexpire(K key, long seconds, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Boolean hexpire(K key, Duration seconds, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param expireArgs the {@link ExpireArgs}.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Boolean hexpire(K key, Duration seconds, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Boolean hexpireat(K key, long timestamp, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Boolean hexpireat(K key, long timestamp, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Boolean hexpireat(K key, Date timestamp, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Boolean hexpireat(K key, Date timestamp, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Boolean hexpireat(K key, Instant timestamp, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Boolean hexpireat(K key, Instant timestamp, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Get the time to live for one or more fields in as unix timestamp in seconds.
+     *
+     * @param key the key.
+     * @param fields one or more fields to get the TTL for.
+     * @return Long integer-reply in seconds, or a negative value in order 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 7.0
+     */
+    Long hexpiretime(K key, K... fields);
+
+    /**
+     * Remove the expiration from one or more fields.
+     *
+     * @param key the key.
+     * @param fields one or more fields to remove the TTL for.
+     * @return Boolean integer-reply specifically:
+     *
+     *         {@code true} if the timeout was removed. {@code false} if {@code key} does not exist or does not have an
+     *         associated timeout.
+     */
+    Boolean hpersist(K key, K... fields);
 }
diff --git a/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java
index 07faab4c2e..516e4ab072 100644
--- a/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java
+++ b/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java
@@ -146,56 +146,6 @@ public interface RedisKeyCommands<K, V> {
      */
     Boolean expire(K key, Duration seconds, ExpireArgs expireArgs);
 
-    /**
-     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key of the fields.
-     * @param seconds the seconds type: long.
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    Boolean hexpire(K key, long seconds, List<V> fields);
-
-    /**
-     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key of the fields.
-     * @param seconds the seconds type: long.
-     * @param expireArgs the expire arguments.
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    Boolean hexpire(K key, long seconds, ExpireArgs expireArgs, List<V> fields);
-
-    /**
-     * Set the time to live for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key.
-     * @param seconds the TTL {@link Duration}
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    Boolean hexpire(K key, Duration seconds, List<V> fields);
-
-    /**
-     * Set the time to live for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key.
-     * @param seconds the TTL {@link Duration}
-     * @param expireArgs the {@link ExpireArgs}.
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    Boolean hexpire(K key, Duration seconds, ExpireArgs expireArgs, List<V> fields);
-
     /**
      * Set the expiration for a key as a UNIX timestamp.
      *
diff --git a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionHashAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionHashAsyncCommands.java
index dd99fd23b0..442444c6ed 100644
--- a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionHashAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionHashAsyncCommands.java
@@ -19,9 +19,7 @@
  */
 package io.lettuce.core.cluster.api.async;
 
-import java.util.List;
-import java.util.Map;
-
+import io.lettuce.core.ExpireArgs;
 import io.lettuce.core.KeyScanCursor;
 import io.lettuce.core.KeyValue;
 import io.lettuce.core.MapScanCursor;
@@ -32,6 +30,12 @@
 import io.lettuce.core.output.KeyValueStreamingChannel;
 import io.lettuce.core.output.ValueStreamingChannel;
 
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
 /**
  * Asynchronous executed commands on a node selection for Hashes (Key-Value pairs).
  *
@@ -428,4 +432,152 @@ public interface NodeSelectionHashAsyncCommands<K, V> {
      * @return Long count of the keys.
      */
     AsyncExecutions<Long> hvals(ValueStreamingChannel<V> channel, K key);
+
+    /**
+     * Set the time to live (in seconds) for one or more fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    AsyncExecutions<Boolean> hexpire(K key, long seconds, K... fields);
+
+    /**
+     * Set the time to live (in seconds) for one or more fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    AsyncExecutions<Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    AsyncExecutions<Boolean> hexpire(K key, Duration seconds, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param expireArgs the {@link ExpireArgs}.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    AsyncExecutions<Boolean> hexpire(K key, Duration seconds, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    AsyncExecutions<Boolean> hexpireat(K key, long timestamp, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    AsyncExecutions<Boolean> hexpireat(K key, long timestamp, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    AsyncExecutions<Boolean> hexpireat(K key, Date timestamp, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    AsyncExecutions<Boolean> hexpireat(K key, Date timestamp, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    AsyncExecutions<Boolean> hexpireat(K key, Instant timestamp, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    AsyncExecutions<Boolean> hexpireat(K key, Instant timestamp, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Get the time to live for one or more fields in as unix timestamp in seconds.
+     *
+     * @param key the key.
+     * @param fields one or more fields to get the TTL for.
+     * @return Long integer-reply in seconds, or a negative value in order 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 7.0
+     */
+    AsyncExecutions<Long> hexpiretime(K key, K... fields);
+
+    /**
+     * Remove the expiration from one or more fields.
+     *
+     * @param key the key.
+     * @param fields one or more fields to remove the TTL for.
+     * @return Boolean integer-reply specifically:
+     *
+     *         {@code true} if the timeout was removed. {@code false} if {@code key} does not exist or does not have an
+     *         associated timeout.
+     */
+    AsyncExecutions<Boolean> hpersist(K key, K... fields);
 }
diff --git a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java
index 422111c0ea..44bbd168a3 100644
--- a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java
@@ -146,56 +146,6 @@ public interface NodeSelectionKeyAsyncCommands<K, V> {
      */
     AsyncExecutions<Boolean> expire(K key, Duration seconds, ExpireArgs expireArgs);
 
-    /**
-     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key of the fields.
-     * @param seconds the seconds type: long.
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    AsyncExecutions<Boolean> hexpire(K key, long seconds, List<V> fields);
-
-    /**
-     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key of the fields.
-     * @param seconds the seconds type: long.
-     * @param expireArgs the expire arguments.
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    AsyncExecutions<Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, List<V> fields);
-
-    /**
-     * Set the time to live for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key.
-     * @param seconds the TTL {@link Duration}
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    AsyncExecutions<Boolean> hexpire(K key, Duration seconds, List<V> fields);
-
-    /**
-     * Set the time to live for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key.
-     * @param seconds the TTL {@link Duration}
-     * @param expireArgs the {@link ExpireArgs}.
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    AsyncExecutions<Boolean> hexpire(K key, Duration seconds, ExpireArgs expireArgs, List<V> fields);
-
     /**
      * Set the expiration for a key as a UNIX timestamp.
      *
diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionHashCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionHashCommands.java
index 4d9a3c694b..f2aeaf52b4 100644
--- a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionHashCommands.java
+++ b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionHashCommands.java
@@ -19,9 +19,7 @@
  */
 package io.lettuce.core.cluster.api.sync;
 
-import java.util.List;
-import java.util.Map;
-
+import io.lettuce.core.ExpireArgs;
 import io.lettuce.core.KeyScanCursor;
 import io.lettuce.core.KeyValue;
 import io.lettuce.core.MapScanCursor;
@@ -32,6 +30,12 @@
 import io.lettuce.core.output.KeyValueStreamingChannel;
 import io.lettuce.core.output.ValueStreamingChannel;
 
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
 /**
  * Synchronous executed commands on a node selection for Hashes (Key-Value pairs).
  *
@@ -428,4 +432,152 @@ public interface NodeSelectionHashCommands<K, V> {
      * @return Long count of the keys.
      */
     Executions<Long> hvals(ValueStreamingChannel<V> channel, K key);
+
+    /**
+     * Set the time to live (in seconds) for one or more fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Executions<Boolean> hexpire(K key, long seconds, K... fields);
+
+    /**
+     * Set the time to live (in seconds) for one or more fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Executions<Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Executions<Boolean> hexpire(K key, Duration seconds, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param expireArgs the {@link ExpireArgs}.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Executions<Boolean> hexpire(K key, Duration seconds, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Executions<Boolean> hexpireat(K key, long timestamp, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Executions<Boolean> hexpireat(K key, long timestamp, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Executions<Boolean> hexpireat(K key, Date timestamp, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Executions<Boolean> hexpireat(K key, Date timestamp, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Executions<Boolean> hexpireat(K key, Instant timestamp, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Executions<Boolean> hexpireat(K key, Instant timestamp, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Get the time to live for one or more fields in as unix timestamp in seconds.
+     *
+     * @param key the key.
+     * @param fields one or more fields to get the TTL for.
+     * @return Long integer-reply in seconds, or a negative value in order 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 7.0
+     */
+    Executions<Long> hexpiretime(K key, K... fields);
+
+    /**
+     * Remove the expiration from one or more fields.
+     *
+     * @param key the key.
+     * @param fields one or more fields to remove the TTL for.
+     * @return Boolean integer-reply specifically:
+     *
+     *         {@code true} if the timeout was removed. {@code false} if {@code key} does not exist or does not have an
+     *         associated timeout.
+     */
+    Executions<Boolean> hpersist(K key, K... fields);
 }
diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java
index b523434d24..398fcf1f65 100644
--- a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java
+++ b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java
@@ -19,11 +19,6 @@
  */
 package io.lettuce.core.cluster.api.sync;
 
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Date;
-import java.util.List;
-
 import io.lettuce.core.CopyArgs;
 import io.lettuce.core.ExpireArgs;
 import io.lettuce.core.KeyScanArgs;
@@ -37,6 +32,11 @@
 import io.lettuce.core.output.KeyStreamingChannel;
 import io.lettuce.core.output.ValueStreamingChannel;
 
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+
 /**
  * Synchronous executed commands on a node selection for Keys (Key manipulation/querying).
  *
@@ -146,56 +146,6 @@ public interface NodeSelectionKeyCommands<K, V> {
      */
     Executions<Boolean> expire(K key, Duration seconds, ExpireArgs expireArgs);
 
-    /**
-     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key of the fields.
-     * @param seconds the seconds type: long.
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    Executions<Boolean> hexpire(K key, long seconds, List<V> fields);
-
-    /**
-     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key of the fields.
-     * @param seconds the seconds type: long.
-     * @param expireArgs the expire arguments.
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    Executions<Boolean> hexpire(K key, long seconds, ExpireArgs expireArgs, List<V> fields);
-
-    /**
-     * Set the time to live for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key.
-     * @param seconds the TTL {@link Duration}
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    Executions<Boolean> hexpire(K key, Duration seconds, List<V> fields);
-
-    /**
-     * Set the time to live for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key.
-     * @param seconds the TTL {@link Duration}
-     * @param expireArgs the {@link ExpireArgs}.
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    Executions<Boolean> hexpire(K key, Duration seconds, ExpireArgs expireArgs, List<V> fields);
-
     /**
      * Set the expiration for a key as a UNIX timestamp.
      *
diff --git a/src/main/java/io/lettuce/core/protocol/CommandType.java b/src/main/java/io/lettuce/core/protocol/CommandType.java
index bd4c04c982..7207a50dab 100644
--- a/src/main/java/io/lettuce/core/protocol/CommandType.java
+++ b/src/main/java/io/lettuce/core/protocol/CommandType.java
@@ -46,7 +46,7 @@ public enum CommandType implements ProtocolKeyword {
 
     // Keys
 
-    COPY, DEL, DUMP, EXISTS, HEXPIRE, EXPIRE, EXPIREAT, EXPIRETIME, KEYS, MIGRATE, MOVE, OBJECT, PERSIST, PEXPIRE, PEXPIREAT, PEXPIRETIME, PTTL, RANDOMKEY, RENAME, RENAMENX, RESTORE, TOUCH, TTL, TYPE, SCAN, UNLINK,
+    COPY, DEL, DUMP, EXISTS, HEXPIRE, EXPIRE, HEXPIREAT, EXPIREAT, HEXPIRETIME, EXPIRETIME, KEYS, MIGRATE, MOVE, OBJECT, HPERSIST, PERSIST, PEXPIRE, PEXPIREAT, PEXPIRETIME, PTTL, RANDOMKEY, RENAME, RENAMENX, RESTORE, TOUCH, TTL, TYPE, SCAN, UNLINK,
 
     // String
 
diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisHashCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisHashCoroutinesCommands.kt
index 6ab7746601..34a4228f49 100644
--- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisHashCoroutinesCommands.kt
+++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisHashCoroutinesCommands.kt
@@ -20,8 +20,17 @@
 
 package io.lettuce.core.api.coroutines
 
-import io.lettuce.core.*
+import io.lettuce.core.ExperimentalLettuceCoroutinesApi
+import io.lettuce.core.ExpireArgs
 import kotlinx.coroutines.flow.Flow
+import io.lettuce.core.KeyScanCursor
+import io.lettuce.core.KeyValue
+import io.lettuce.core.MapScanCursor
+import io.lettuce.core.ScanArgs
+import io.lettuce.core.ScanCursor
+import java.time.Duration
+import java.time.Instant
+import java.util.*
 
 /**
  * Coroutine executed commands for Hashes (Key-Value pairs).
@@ -298,5 +307,153 @@ interface RedisHashCoroutinesCommands<K : Any, V : Any> {
      */
     fun hvals(key: K): Flow<V>
 
+    /**
+     * Set the time to live (in seconds) for one or more fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: `true` if the timeout was set. `false` if `key` does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    suspend fun hexpire(key: K, seconds: Long, vararg fields: K): Boolean?
+
+    /**
+     * Set the time to live (in seconds) for one or more fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: `true` if the timeout was set. `false` if `key` does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    suspend fun hexpire(key: K, seconds: Long, expireArgs: ExpireArgs, vararg fields: K): Boolean?
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL [Duration]
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: `true` if the timeout was set. `false` if `key` does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    suspend fun hexpire(key: K, seconds: Duration, vararg fields: K): Boolean?
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL [Duration]
+     * @param expireArgs the [ExpireArgs].
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: `true` if the timeout was set. `false` if `key` does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    suspend fun hexpire(key: K, seconds: Duration, expireArgs: ExpireArgs, vararg fields: K): Boolean?
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: `true` if the timeout was set. `false` if `key` does not
+     *         exist or the timeout could not be set (see: `EXPIRE`).
+     * @since 7.0
+     */
+    suspend fun hexpireat(key: K, timestamp: Long, vararg fields: K): Boolean?
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: `true` if the timeout was set. `false` if `key` does not
+     *         exist or the timeout could not be set (see: `EXPIRE`).
+     * @since 7.0
+     */
+    suspend fun hexpireat(key: K, timestamp: Long, expireArgs: ExpireArgs, vararg fields: K): Boolean?
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: `true` if the timeout was set. `false` if `key` does not
+     *         exist or the timeout could not be set (see: `EXPIRE`).
+     * @since 7.0
+     */
+    suspend fun hexpireat(key: K, timestamp: Date, vararg fields: K): Boolean?
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: `true` if the timeout was set. `false` if `key` does not
+     *         exist or the timeout could not be set (see: `EXPIRE`).
+     * @since 7.0
+     */
+    suspend fun hexpireat(key: K, timestamp: Date, expireArgs: ExpireArgs, vararg fields: K): Boolean?
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: `true` if the timeout was set. `false` if `key` does not
+     *         exist or the timeout could not be set (see: `EXPIRE`).
+     * @since 7.0
+     */
+    suspend fun hexpireat(key: K, timestamp: Instant, vararg fields: K): Boolean?
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: `true` if the timeout was set. `false` if `key` does not
+     *         exist or the timeout could not be set (see: `EXPIRE`).
+     * @since 7.0
+     */
+    suspend fun hexpireat(key: K, timestamp: Instant, expireArgs: ExpireArgs, vararg fields: K): Boolean?
+
+    /**
+     * Get the time to live for one or more fields in as unix timestamp in seconds.
+     *
+     * @param key the key.
+     * @param fields one or more fields to get the TTL for.
+     * @return Long integer-reply in seconds, or a negative value in order to signal an error. The command returns `-1` if
+     *         the key exists but has no associated expiration time. The command returns `-2` if the key does not exist.
+     * @since 7.0
+     */
+    suspend fun hexpiretime(key: K, vararg fields: K): Long?
+
+    /**
+     * Remove the expiration from one or more fields.
+     *
+     * @param key the key.
+     * @param fields one or more fields to remove the TTL for.
+     * @return Boolean integer-reply specifically:
+     *
+     *         `true` if the timeout was removed. `false` if `key` does not exist or does not have an
+     *         associated timeout.
+     */
+    suspend fun hpersist(key: K, vararg fields: K): Boolean?
+
 }
 
diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisHashCoroutinesCommandsImpl.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisHashCoroutinesCommandsImpl.kt
index ec1a488735..8a1d959e54 100644
--- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisHashCoroutinesCommandsImpl.kt
+++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisHashCoroutinesCommandsImpl.kt
@@ -26,6 +26,9 @@ import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.toList
 import kotlinx.coroutines.reactive.asFlow
 import kotlinx.coroutines.reactive.awaitFirstOrNull
+import java.time.Duration
+import java.time.Instant
+import java.util.*
 
 
 /**
@@ -113,5 +116,60 @@ internal class RedisHashCoroutinesCommandsImpl<K : Any, V : Any>(internal val op
 
     override fun hvals(key: K): Flow<V> = ops.hvals(key).asFlow()
 
+    override suspend fun hexpire(key: K, seconds: Long, vararg fields: K): Boolean? =
+        ops.hexpire(key, seconds, *fields).awaitFirstOrNull()
+
+    override suspend fun hexpire(key: K, seconds: Long, expireArgs: ExpireArgs, vararg fields: K): Boolean? =
+        ops.hexpire(key, seconds, expireArgs, *fields).awaitFirstOrNull()
+
+    override suspend fun hexpire(key: K, seconds: Duration, vararg fields: K): Boolean? =
+        ops.hexpire(key, seconds, *fields).awaitFirstOrNull()
+
+    override suspend fun hexpire(
+        key: K,
+        seconds: Duration,
+        expireArgs: ExpireArgs,
+        vararg fields: K
+    ): Boolean? =
+        ops.hexpire(key, seconds, expireArgs, *fields).awaitFirstOrNull()
+
+    override suspend fun hexpireat(key: K, timestamp: Date, vararg fields: K): Boolean? =
+        ops.hexpireat(key, timestamp, *fields).awaitFirstOrNull()
+
+    override suspend fun hexpireat(
+        key: K,
+        timestamp: Long,
+        expireArgs: ExpireArgs,
+        vararg fields: K
+    ): Boolean? =
+        ops.hexpireat(key, timestamp, expireArgs, *fields).awaitFirstOrNull()
+
+    override suspend fun hexpireat(key: K, timestamp: Instant, vararg fields: K): Boolean? =
+        ops.hexpireat(key, timestamp, *fields).awaitFirstOrNull()
+
+    override suspend fun hexpireat(
+        key: K,
+        timestamp: Instant,
+        expireArgs: ExpireArgs,
+        vararg fields: K
+    ): Boolean? =
+        ops.hexpireat(key, timestamp, expireArgs, *fields).awaitFirstOrNull()
+
+    override suspend fun hexpireat(key: K, timestamp: Long, vararg fields: K): Boolean? =
+        ops.hexpireat(key, timestamp, *fields).awaitFirstOrNull()
+
+    override suspend fun hexpireat(
+        key: K,
+        timestamp: Date,
+        expireArgs: ExpireArgs,
+        vararg fields: K
+    ): Boolean? =
+        ops.hexpireat(key, timestamp, expireArgs, *fields).awaitFirstOrNull()
+
+    override suspend fun hexpiretime(key: K, vararg fields: K): Long? =
+        ops.hexpiretime(key).awaitFirstOrNull()
+
+    override suspend fun hpersist(key: K, vararg fields: K): Boolean? = ops.hpersist(key).awaitFirstOrNull()
+
 }
 
diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
index f979c8bc4e..72aa35c614 100644
--- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
+++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
@@ -20,11 +20,19 @@
 
 package io.lettuce.core.api.coroutines
 
-import io.lettuce.core.*
+import io.lettuce.core.CopyArgs
+import io.lettuce.core.ExperimentalLettuceCoroutinesApi
+import io.lettuce.core.ExpireArgs
+import io.lettuce.core.KeyScanCursor
+import io.lettuce.core.MigrateArgs
+import io.lettuce.core.RestoreArgs
+import io.lettuce.core.ScanArgs
+import io.lettuce.core.ScanCursor
+import io.lettuce.core.SortArgs
 import kotlinx.coroutines.flow.Flow
 import java.time.Duration
 import java.time.Instant
-import java.util.*
+import java.util.Date
 
 /**
  * Coroutine executed commands for Keys (Key manipulation/querying).
@@ -136,56 +144,6 @@ interface RedisKeyCoroutinesCommands<K : Any, V : Any> {
      */
     suspend fun expire(key: K, seconds: Duration, expireArgs: ExpireArgs): Boolean?
 
-    /**
-     * Set the time to live (in seconds) for a [List] of fields, belonging to a certain key.
-     *
-     * @param key the key of the fields.
-     * @param seconds the seconds type: long.
-     * @param fields a [List] of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: `true` if the timeout was set. `false` if `key` does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    suspend fun hexpire(key: K, seconds: Long, fields: List<V>): Boolean?
-
-    /**
-     * Set the time to live (in seconds) for a [List] of fields, belonging to a certain key.
-     *
-     * @param key the key of the fields.
-     * @param seconds the seconds type: long.
-     * @param expireArgs the expire arguments.
-     * @param fields a [List] of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: `true` if the timeout was set. `false` if `key` does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    suspend fun hexpire(key: K, seconds: Long, expireArgs: ExpireArgs, fields: List<V>): Boolean?
-
-    /**
-     * Set the time to live for a [List] of fields, belonging to a certain key.
-     *
-     * @param key the key.
-     * @param seconds the TTL [Duration]
-     * @param fields a [List] of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: `true` if the timeout was set. `false` if `key` does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    suspend fun hexpire(key: K, seconds: Duration, fields: List<V>): Boolean?
-
-    /**
-     * Set the time to live for a [List] of fields, belonging to a certain key.
-     *
-     * @param key the key.
-     * @param seconds the TTL [Duration]
-     * @param expireArgs the [ExpireArgs].
-     * @param fields a [List] of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: `true` if the timeout was set. `false` if `key` does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    suspend fun hexpire(key: K, seconds: Duration, expireArgs: ExpireArgs, fields: List<V>): Boolean?
-
     /**
      * Set the expiration for a key as a UNIX timestamp.
      *
diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt
index d088d03ad8..5b62a92d3c 100644
--- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt
+++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt
@@ -74,24 +74,6 @@ internal class RedisKeyCoroutinesCommandsImpl<K : Any, V : Any>(internal val ops
     ): Boolean? =
         ops.expire(key, seconds, expireArgs).awaitFirstOrNull()
 
-    override suspend fun hexpire(key: K, seconds: Long, fields: List<V>): Boolean? =
-        ops.hexpire(key, seconds, fields).awaitFirstOrNull()
-
-    override suspend fun hexpire(key: K, seconds: Long, expireArgs: ExpireArgs, fields: List<V>): Boolean? =
-        ops.hexpire(key, seconds, expireArgs, fields).awaitFirstOrNull()
-
-
-    override suspend fun hexpire(key: K, seconds: Duration, fields: List<V>): Boolean? =
-        ops.hexpire(key, seconds, fields).awaitFirstOrNull()
-
-    override suspend fun hexpire(
-        key: K,
-        seconds: Duration,
-        expireArgs: ExpireArgs,
-        fields: List<V>
-    ): Boolean? =
-        ops.hexpire(key, seconds, expireArgs, fields).awaitFirstOrNull()
-
     override suspend fun expireat(key: K, timestamp: Date): Boolean? =
         ops.expireat(key, timestamp).awaitFirstOrNull()
 
diff --git a/src/main/templates/io/lettuce/core/api/RedisHashCommands.java b/src/main/templates/io/lettuce/core/api/RedisHashCommands.java
index ddef1ce24f..96dfe525eb 100644
--- a/src/main/templates/io/lettuce/core/api/RedisHashCommands.java
+++ b/src/main/templates/io/lettuce/core/api/RedisHashCommands.java
@@ -428,4 +428,151 @@ public interface RedisHashCommands<K, V> {
      */
     Long hvals(ValueStreamingChannel<V> channel, K key);
 
+    /**
+     * Set the time to live (in seconds) for one or more fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Boolean hexpire(K key, long seconds, K... fields);
+
+    /**
+     * Set the time to live (in seconds) for one or more fields, belonging to a certain key.
+     *
+     * @param key the key of the fields.
+     * @param seconds the seconds type: long.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Boolean hexpire(K key, long seconds, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Boolean hexpire(K key, Duration seconds, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key.
+     *
+     * @param key the key.
+     * @param seconds the TTL {@link Duration}
+     * @param expireArgs the {@link ExpireArgs}.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set.
+     * @since 7.0
+     */
+    Boolean hexpire(K key, Duration seconds, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Boolean hexpireat(K key, long timestamp, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Boolean hexpireat(K key, long timestamp, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Boolean hexpireat(K key, Date timestamp, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Boolean hexpireat(K key, Date timestamp, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Boolean hexpireat(K key, Instant timestamp, K... fields);
+
+    /**
+     * Set the time to live for one or more fields, belonging to a certain key as a UNIX timestamp.
+     *
+     * @param key the key.
+     * @param timestamp the timestamp type: posix time.
+     * @param expireArgs the expire arguments.
+     * @param fields one or more fields to set the TTL for.
+     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
+     *         exist or the timeout could not be set (see: {@code EXPIRE}).
+     * @since 7.0
+     */
+    Boolean hexpireat(K key, Instant timestamp, ExpireArgs expireArgs, K... fields);
+
+    /**
+     * Get the time to live for one or more fields in as unix timestamp in seconds.
+     *
+     * @param key the key.
+     * @param fields one or more fields to get the TTL for.
+     * @return Long integer-reply in seconds, or a negative value in order 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 7.0
+     */
+    Long hexpiretime(K key, K... fields);
+
+    /**
+     * Remove the expiration from one or more fields.
+     *
+     * @param key the key.
+     * @param fields one or more fields to remove the TTL for.
+     * @return Boolean integer-reply specifically:
+     *
+     *         {@code true} if the timeout was removed. {@code false} if {@code key} does not exist or does not have an
+     *         associated timeout.
+     */
+    Boolean hpersist(K key, K... fields);
 }
diff --git a/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java b/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java
index 6f8ba3c27b..bdd3568991 100644
--- a/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java
+++ b/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java
@@ -138,56 +138,6 @@ public interface RedisKeyCommands<K, V> {
      */
     Boolean expire(K key, Duration seconds, ExpireArgs expireArgs);
 
-    /**
-     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key of the fields.
-     * @param seconds the seconds type: long.
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    Boolean hexpire(K key, long seconds, List<V> fields);
-
-    /**
-     * Set the time to live (in seconds) for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key of the fields.
-     * @param seconds the seconds type: long.
-     * @param expireArgs the expire arguments.
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    Boolean hexpire(K key, long seconds, ExpireArgs expireArgs, List<V> fields);
-
-    /**
-     * Set the time to live for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key.
-     * @param seconds the TTL {@link Duration}
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    Boolean hexpire(K key, Duration seconds, List<V> fields);
-
-    /**
-     * Set the time to live for a {@link List} of fields, belonging to a certain key.
-     *
-     * @param key the key.
-     * @param seconds the TTL {@link Duration}
-     * @param expireArgs the {@link ExpireArgs}.
-     * @param fields a {@link List} of fields to set the TTL for.
-     * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not
-     *         exist or the timeout could not be set.
-     * @since 7.0
-     */
-    Boolean hexpire(K key, Duration seconds, ExpireArgs expireArgs, List<V> fields);
-
     /**
      * Set the expiration for a key as a UNIX timestamp.
      *
diff --git a/src/test/java/io/lettuce/core/RedisCommandBuilderUnitTests.java b/src/test/java/io/lettuce/core/RedisCommandBuilderUnitTests.java
index 2136ff0a92..01b5be2241 100644
--- a/src/test/java/io/lettuce/core/RedisCommandBuilderUnitTests.java
+++ b/src/test/java/io/lettuce/core/RedisCommandBuilderUnitTests.java
@@ -1,17 +1,14 @@
 package io.lettuce.core;
 
-import static org.assertj.core.api.Assertions.*;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.EmptyByteBuf;
-import org.junit.jupiter.api.Test;
-
 import io.lettuce.core.codec.StringCodec;
 import io.lettuce.core.protocol.Command;
+import io.netty.buffer.ByteBuf;
 import io.netty.buffer.Unpooled;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+
+import static org.assertj.core.api.Assertions.assertThat;
 
 /**
  * Unit tests for {@link RedisCommandBuilder}.
@@ -20,7 +17,10 @@
  */
 class RedisCommandBuilderUnitTests {
     public static final String MY_KEY = "hKey";
-    public static final String MY_FIELD = "hField";
+    public static final String MY_FIELD1 = "hField1";
+    public static final String MY_FIELD2 = "hField2";
+    public static final String MY_FIELD3 = "hField3";
+
 
     RedisCommandBuilder<String, String> sut = new RedisCommandBuilder<>(StringCodec.UTF8);
 
@@ -37,11 +37,50 @@ void shouldCorrectlyConstructXreadgroup() {
     @Test
     void shouldCorrectlyConstructHexpire() {
 
-        Command<String, String, ?> command = sut.hexpire(MY_KEY, 1, ExpireArgs.Builder.nx(), Collections.singletonList(MY_FIELD));
+        Command<String, String, ?> command =
+                sut.hexpire(MY_KEY, 1, ExpireArgs.Builder.nx(), MY_FIELD1, MY_FIELD2, MY_FIELD3);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*8\r\n" + "$7\r\n" + "HEXPIRE\r\n" + "$4\r\n"
+                + "hKey\r\n" + "$1\r\n" + "1\r\n" + "$2\r\n" + "NX\r\n" + "$1\r\n" + "3\r\n" + "$7\r\n" + "hField1\r\n"
+                + "$7\r\n" + "hField2\r\n" + "$7\r\n" + "hField3\r\n");
+    }
+
+    @Test
+    void shouldCorrectlyConstructHexpireat() {
+
+        Command<String, String, ?> command =
+                sut.hexpireat(MY_KEY, 1, ExpireArgs.Builder.nx(), MY_FIELD1, MY_FIELD2, MY_FIELD3);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*8\r\n" + "$9\r\n" + "HEXPIREAT\r\n" + "$4\r\n"
+                + "hKey\r\n" + "$1\r\n" + "1\r\n" + "$2\r\n" + "NX\r\n" + "$1\r\n" + "3\r\n" + "$7\r\n" + "hField1\r\n"
+                + "$7\r\n" + "hField2\r\n" + "$7\r\n" + "hField3\r\n");
+    }
+
+    @Test
+    void shouldCorrectlyConstructHexpiretime() {
+
+        Command<String, String, ?> command = sut.hexpiretime(MY_KEY, MY_FIELD1, MY_FIELD2, MY_FIELD3);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*6\r\n" + "$11\r\n" + "HEXPIRETIME\r\n" + "$4\r\n"
+                + "hKey\r\n" + "$1\r\n" + "3\r\n" + "$7\r\n" + "hField1\r\n" + "$7\r\n" + "hField2\r\n" + "$7\r\n"
+                + "hField3\r\n");
+    }
+
+    @Test
+    void shouldCorrectlyConstructHpersist() {
+
+        Command<String, String, ?> command = sut.hpersist(MY_KEY, MY_FIELD1, MY_FIELD2, MY_FIELD3);
         ByteBuf buf = Unpooled.directBuffer();
         command.encode(buf);
 
-        assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*6\r\n" + "$7\r\n" + "HEXPIRE\r\n" + "$4\r\n" + "hKey\r\n"
-                + "$1\r\n" + "1\r\n" + "$2\r\n" + "NX\r\n" + "$1\r\n" + "1\r\n" + "$6\r\n" + "hField\r\n");
+        assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*6\r\n" + "$8\r\n" + "HPERSIST\r\n" + "$4\r\n"
+                + "hKey\r\n" + "$1\r\n" + "3\r\n" + "$7\r\n" + "hField1\r\n" + "$7\r\n" + "hField2\r\n" + "$7\r\n"
+                + "hField3\r\n");
     }
 }
diff --git a/src/test/java/io/lettuce/core/commands/HashCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/HashCommandIntegrationTests.java
index e346b29128..0ae5c8e820 100644
--- a/src/test/java/io/lettuce/core/commands/HashCommandIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/commands/HashCommandIntegrationTests.java
@@ -19,24 +19,9 @@
  */
 package io.lettuce.core.commands;
 
-import static org.assertj.core.api.Assertions.*;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.inject.Inject;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestInstance;
-import org.junit.jupiter.api.extension.ExtendWith;
-
-import io.lettuce.core.KeyValue;
+import io.lettuce.core.ExpireArgs;
 import io.lettuce.core.KeyScanCursor;
+import io.lettuce.core.KeyValue;
 import io.lettuce.core.MapScanCursor;
 import io.lettuce.core.ScanArgs;
 import io.lettuce.core.ScanCursor;
@@ -47,6 +32,25 @@
 import io.lettuce.test.LettuceExtension;
 import io.lettuce.test.ListStreamingAdapter;
 import io.lettuce.test.condition.EnabledOnCommand;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import javax.inject.Inject;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.offset;
+import static org.awaitility.Awaitility.await;
 
 /**
  * Integration tests for {@link io.lettuce.core.api.sync.RedisHashCommands}.
@@ -58,6 +62,9 @@
 @ExtendWith(LettuceExtension.class)
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
 public class HashCommandIntegrationTests extends TestSupport {
+    public static final String MY_KEY = "hKey";
+    public static final String MY_FIELD = "hField";
+    public static final String MY_VALUE = "hValue";
 
     private final RedisCommands<String, String> redis;
 
@@ -541,6 +548,82 @@ void hscanNoValuesMatch() {
         assertThat(cursor.getKeys()).hasSize(11);
     }
 
+    @Test
+    @EnabledOnCommand("HEXPIRE")
+    void hexpire() {
+        assertThat(redis.hset(MY_KEY, MY_FIELD, MY_VALUE)).isTrue();
+        // the below settings are required until the solution is able to support listpack entries
+        // see TODOs in https://github.com/redis/redis/pull/13172 for more details
+        assertThat(redis.configSet("hash-max-listpack-entries","0")).isEqualTo("OK");
+        assertThat(redis.configSet("set-max-listpack-value","0")).isEqualTo("OK");
+
+        assertThat(redis.hexpire(MY_KEY, 1, MY_FIELD)).isTrue();
+
+        await().until(() -> redis.hget(MY_KEY, MY_FIELD) == null);
+    }
+
+    @Test
+    @EnabledOnCommand("HEXPIRE")
+    void hexpireExpireArgs() {
+        assertThat(redis.hset(MY_KEY, MY_FIELD, MY_VALUE)).isTrue();
+        // the below settings are required until the solution is able to support listpack entries
+        // see TODOs in https://github.com/redis/redis/pull/13172 for more details
+        assertThat(redis.configSet("hash-max-listpack-entries","0")).isEqualTo("OK");
+        assertThat(redis.configSet("set-max-listpack-value","0")).isEqualTo("OK");
+
+        assertThat(redis.hexpire(MY_KEY, Duration.ofSeconds(1), ExpireArgs.Builder.nx(), MY_FIELD)).isTrue();
+        assertThat(redis.hexpire(MY_KEY, Duration.ofSeconds(1), ExpireArgs.Builder.xx(), MY_FIELD)).isTrue();
+        assertThat(redis.hexpire(MY_KEY, Duration.ofSeconds(10), ExpireArgs.Builder.gt(), MY_FIELD)).isTrue();
+        assertThat(redis.hexpire(MY_KEY, Duration.ofSeconds(1), ExpireArgs.Builder.lt(), MY_FIELD)).isTrue();
+
+        await().until(() -> redis.hget(MY_KEY, MY_FIELD) == null);
+    }
+
+    @Test
+    @EnabledOnCommand("HEXPIREAT")
+    void hexpireat() {
+        // the below settings are required until the solution is able to support listpack entries
+        // see TODOs in https://github.com/redis/redis/pull/13172 for more details
+        assertThat(redis.configSet("hash-max-listpack-entries","0")).isEqualTo("OK");
+        assertThat(redis.configSet("set-max-listpack-value","0")).isEqualTo("OK");
+
+        assertThat(redis.hset(MY_KEY, MY_FIELD, MY_VALUE)).isTrue();
+
+        assertThat(redis.hexpireat(MY_KEY,Instant.now().plusSeconds(1) , MY_FIELD)).isTrue();
+
+        await().until(() -> redis.hget(MY_KEY, MY_FIELD) == null);
+    }
+
+    @Test
+    @EnabledOnCommand("HEXPIRETIME")
+    void hexpiretime() {
+        Date expiration = new Date(System.currentTimeMillis() + 10000);
+        // the below settings are required until the solution is able to support listpack entries
+        // see TODOs in https://github.com/redis/redis/pull/13172 for more details
+        assertThat(redis.configSet("hash-max-listpack-entries","0")).isEqualTo("OK");
+        assertThat(redis.configSet("set-max-listpack-value","0")).isEqualTo("OK");
+
+        assertThat(redis.hset(MY_KEY, MY_FIELD, MY_VALUE)).isTrue();
+        assertThat(redis.hexpireat(MY_KEY, expiration, MY_FIELD)).isTrue();
+
+        assertThat(redis.hexpiretime(MY_KEY, MY_FIELD)).isEqualTo(expiration.getTime() / 1000);
+    }
+
+    @Test
+    @EnabledOnCommand("HPERSIST")
+    void persist() {
+        // the below settings are required until the solution is able to support listpack entries
+        // see TODOs in https://github.com/redis/redis/pull/13172 for more details
+        assertThat(redis.configSet("hash-max-listpack-entries","0")).isEqualTo("OK");
+        assertThat(redis.configSet("set-max-listpack-value","0")).isEqualTo("OK");
+
+        assertThat(redis.hpersist(MY_KEY, MY_FIELD)).isFalse();
+        assertThat(redis.hset(MY_KEY, MY_FIELD, MY_VALUE)).isTrue();
+        assertThat(redis.hpersist(MY_KEY, MY_FIELD)).isFalse();
+        assertThat(redis.hexpire(MY_KEY, 1, MY_FIELD)).isTrue();
+        assertThat(redis.hpersist(MY_KEY, MY_FIELD)).isTrue();
+    }
+
     void setup100KeyValues(Map<String, String> expect) {
         for (int i = 0; i < 100; i++) {
             expect.put(key + i, value + 1);
diff --git a/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
index 6a67e9706e..2188595fc1 100644
--- a/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
@@ -19,20 +19,6 @@
  */
 package io.lettuce.core.commands;
 
-import static org.assertj.core.api.Assertions.*;
-import static org.awaitility.Awaitility.await;
-
-import java.time.Duration;
-import java.time.Instant;
-import java.util.*;
-
-import javax.inject.Inject;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestInstance;
-import org.junit.jupiter.api.extension.ExtendWith;
-
 import io.lettuce.core.CopyArgs;
 import io.lettuce.core.ExpireArgs;
 import io.lettuce.core.KeyScanArgs;
@@ -46,6 +32,23 @@
 import io.lettuce.test.LettuceExtension;
 import io.lettuce.test.ListStreamingAdapter;
 import io.lettuce.test.condition.EnabledOnCommand;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import javax.inject.Inject;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 /**
  * Integration tests for {@link io.lettuce.core.api.sync.RedisKeyCommands}.
@@ -58,12 +61,6 @@
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
 public class KeyCommandIntegrationTests extends TestSupport {
 
-    public static final String MY_KEY = "hKey";
-
-    public static final String MY_FIELD = "hField";
-
-    public static final String MY_VALUE = "hValue";
-
     private final RedisCommands<String, String> redis;
 
     @Inject
@@ -194,49 +191,6 @@ void expiretime() {
         assertThat(redis.expiretime(key)).isEqualTo(expiration.getTime() / 1000);
     }
 
-    @Test
-    @EnabledOnCommand("HEXPIRE")
-    void hexpire() {
-        assertThat(redis.hset(MY_KEY, MY_FIELD, MY_VALUE)).isTrue();
-        // the below settings are required until the solution is able to support listpack entries
-        // see TODOs in https://github.com/redis/redis/pull/13172 for more details
-        assertThat(redis.configSet("hash-max-listpack-entries","0")).isEqualTo("OK");
-        assertThat(redis.configSet("set-max-listpack-value","0")).isEqualTo("OK");
-
-        assertThat(redis.hexpire(MY_KEY, 1, Collections.singletonList(MY_FIELD))).isTrue();
-
-        await().until(() -> redis.hget(MY_KEY, MY_FIELD) == null);
-    }
-
-    @Test
-    @EnabledOnCommand("HEXPIRE")
-    void hexpireExpireArgs() {
-        assertThat(redis.hset(MY_KEY, MY_FIELD, MY_VALUE)).isTrue();
-        // the below settings are required until the solution is able to support listpack entries
-        // see TODOs in https://github.com/redis/redis/pull/13172 for more details
-        assertThat(redis.configSet("hash-max-listpack-entries","0")).isEqualTo("OK");
-        assertThat(redis.configSet("set-max-listpack-value","0")).isEqualTo("OK");
-
-        assertThat(redis.hexpire(MY_KEY,
-                Duration.ofSeconds(1),
-                ExpireArgs.Builder.nx(),
-                Collections.singletonList(MY_FIELD))).isTrue();
-        assertThat(redis.hexpire(MY_KEY,
-                Duration.ofSeconds(1),
-                ExpireArgs.Builder.xx(),
-                Collections.singletonList(MY_FIELD))).isTrue();
-        assertThat(redis.hexpire(MY_KEY,
-                Duration.ofSeconds(10),
-                ExpireArgs.Builder.gt(),
-                Collections.singletonList(MY_FIELD))).isTrue();
-        assertThat(redis.hexpire(MY_KEY,
-                Duration.ofSeconds(1),
-                ExpireArgs.Builder.lt(),
-                Collections.singletonList(MY_FIELD))).isTrue();
-
-        await().until(() -> redis.hget(MY_KEY, MY_FIELD) == null);
-    }
-
     @Test
     void keys() {
         assertThat(redis.keys("*")).isEqualTo(list());

From e42c7ba3923bb46455369a36347f2a473a31b874 Mon Sep 17 00:00:00 2001
From: Tihomir Mateev <tihomir.mateev@gmail.com>
Date: Thu, 25 Apr 2024 10:11:54 +0300
Subject: [PATCH 4/7] Make sure we reset the configuration setting after the
 new hash commands were tested

---
 .../core/commands/HashCommandIntegrationTests.java  | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/src/test/java/io/lettuce/core/commands/HashCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/HashCommandIntegrationTests.java
index 0ae5c8e820..6c1fae02e7 100644
--- a/src/test/java/io/lettuce/core/commands/HashCommandIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/commands/HashCommandIntegrationTests.java
@@ -32,6 +32,7 @@
 import io.lettuce.test.LettuceExtension;
 import io.lettuce.test.ListStreamingAdapter;
 import io.lettuce.test.condition.EnabledOnCommand;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
@@ -78,6 +79,13 @@ void setUp() {
         this.redis.flushall();
     }
 
+    @AfterEach
+    void tearDown() {
+        // resets the configuration settings to default, would not be needed once listpack is supported
+        assertThat(redis.configSet("hash-max-listpack-entries","128")).isEqualTo("OK");
+        assertThat(redis.configSet("set-max-listpack-value","64")).isEqualTo("OK");
+    }
+
     @Test
     void hdel() {
         assertThat(redis.hdel(key, "one")).isEqualTo(0);
@@ -551,12 +559,12 @@ void hscanNoValuesMatch() {
     @Test
     @EnabledOnCommand("HEXPIRE")
     void hexpire() {
-        assertThat(redis.hset(MY_KEY, MY_FIELD, MY_VALUE)).isTrue();
         // the below settings are required until the solution is able to support listpack entries
         // see TODOs in https://github.com/redis/redis/pull/13172 for more details
         assertThat(redis.configSet("hash-max-listpack-entries","0")).isEqualTo("OK");
         assertThat(redis.configSet("set-max-listpack-value","0")).isEqualTo("OK");
 
+        assertThat(redis.hset(MY_KEY, MY_FIELD, MY_VALUE)).isTrue();
         assertThat(redis.hexpire(MY_KEY, 1, MY_FIELD)).isTrue();
 
         await().until(() -> redis.hget(MY_KEY, MY_FIELD) == null);
@@ -565,12 +573,12 @@ void hexpire() {
     @Test
     @EnabledOnCommand("HEXPIRE")
     void hexpireExpireArgs() {
-        assertThat(redis.hset(MY_KEY, MY_FIELD, MY_VALUE)).isTrue();
         // the below settings are required until the solution is able to support listpack entries
         // see TODOs in https://github.com/redis/redis/pull/13172 for more details
         assertThat(redis.configSet("hash-max-listpack-entries","0")).isEqualTo("OK");
         assertThat(redis.configSet("set-max-listpack-value","0")).isEqualTo("OK");
 
+        assertThat(redis.hset(MY_KEY, MY_FIELD, MY_VALUE)).isTrue();
         assertThat(redis.hexpire(MY_KEY, Duration.ofSeconds(1), ExpireArgs.Builder.nx(), MY_FIELD)).isTrue();
         assertThat(redis.hexpire(MY_KEY, Duration.ofSeconds(1), ExpireArgs.Builder.xx(), MY_FIELD)).isTrue();
         assertThat(redis.hexpire(MY_KEY, Duration.ofSeconds(10), ExpireArgs.Builder.gt(), MY_FIELD)).isTrue();
@@ -588,7 +596,6 @@ void hexpireat() {
         assertThat(redis.configSet("set-max-listpack-value","0")).isEqualTo("OK");
 
         assertThat(redis.hset(MY_KEY, MY_FIELD, MY_VALUE)).isTrue();
-
         assertThat(redis.hexpireat(MY_KEY,Instant.now().plusSeconds(1) , MY_FIELD)).isTrue();
 
         await().until(() -> redis.hget(MY_KEY, MY_FIELD) == null);

From d9e940fb223f0c91e6996fe52247910d78cb2c89 Mon Sep 17 00:00:00 2001
From: Tihomir Mateev <tihomir.mateev@gmail.com>
Date: Thu, 25 Apr 2024 14:54:19 +0300
Subject: [PATCH 5/7] Broke one test because of wrong configuration setting;
 the other is unstable, trying to fix it

---
 .../core/RedisClientIntegrationTests.java     | 27 ++++++++++---------
 .../commands/HashCommandIntegrationTests.java |  2 +-
 2 files changed, 15 insertions(+), 14 deletions(-)

diff --git a/src/test/java/io/lettuce/core/RedisClientIntegrationTests.java b/src/test/java/io/lettuce/core/RedisClientIntegrationTests.java
index 2b78d3f99c..57eb8d2bbc 100644
--- a/src/test/java/io/lettuce/core/RedisClientIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/RedisClientIntegrationTests.java
@@ -1,17 +1,5 @@
 package io.lettuce.core;
 
-import static org.assertj.core.api.Assertions.*;
-
-import java.lang.reflect.Field;
-import java.net.SocketAddress;
-import java.time.Duration;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-import org.junit.jupiter.api.Test;
-
 import io.lettuce.core.api.StatefulRedisConnection;
 import io.lettuce.core.event.command.CommandFailedEvent;
 import io.lettuce.core.event.command.CommandListener;
@@ -27,6 +15,17 @@
 import io.lettuce.test.resource.TestClientResources;
 import io.lettuce.test.settings.TestSettings;
 import io.netty.util.concurrent.EventExecutorGroup;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Field;
+import java.net.SocketAddress;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import static org.assertj.core.api.Assertions.assertThat;
 
 /**
  * Integration tests for {@link RedisClient}.
@@ -138,7 +137,9 @@ void shouldPropagateCommandTimeoutToCommandListener() throws InterruptedExceptio
 
         assertThat(commandListener.started).hasSize(1);
         assertThat(commandListener.succeeded).isEmpty();
-        assertThat(commandListener.failed).hasSize(1).extracting(it -> it.getCommand().getType()).contains(CommandType.BLPOP);
+
+        Wait.untilTrue(() -> commandListener.failed.size() == 1);
+        assertThat(commandListener.failed).extracting(it -> it.getCommand().getType()).contains(CommandType.BLPOP);
 
         FastShutdown.shutdown(client);
     }
diff --git a/src/test/java/io/lettuce/core/commands/HashCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/HashCommandIntegrationTests.java
index 6c1fae02e7..b87102505d 100644
--- a/src/test/java/io/lettuce/core/commands/HashCommandIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/commands/HashCommandIntegrationTests.java
@@ -82,7 +82,7 @@ void setUp() {
     @AfterEach
     void tearDown() {
         // resets the configuration settings to default, would not be needed once listpack is supported
-        assertThat(redis.configSet("hash-max-listpack-entries","128")).isEqualTo("OK");
+        assertThat(redis.configSet("hash-max-listpack-entries","512")).isEqualTo("OK");
         assertThat(redis.configSet("set-max-listpack-value","64")).isEqualTo("OK");
     }
 

From cc380e188b2687e89de4cea27fa8e574274e3da9 Mon Sep 17 00:00:00 2001
From: Tihomir Mateev <tihomir.mateev@gmail.com>
Date: Thu, 25 Apr 2024 15:48:52 +0300
Subject: [PATCH 6/7] Polishing imports

---
 .../core/AbstractRedisAsyncCommands.java      |  14 +-
 .../core/AbstractRedisReactiveCommands.java   |  14 +-
 .../io/lettuce/core/RedisCommandBuilder.java  | 157 +-----------------
 .../core/api/async/RedisKeyAsyncCommands.java |  18 +-
 .../api/sync/NodeSelectionKeyCommands.java    |  10 +-
 .../coroutines/RedisKeyCoroutinesCommands.kt  |  12 +-
 .../commands/KeyCommandIntegrationTests.java  |  35 ++--
 7 files changed, 35 insertions(+), 225 deletions(-)

diff --git a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
index 10331085fb..8b09c6b2e2 100644
--- a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
@@ -21,19 +21,7 @@
 
 import io.lettuce.core.GeoArgs.Unit;
 import io.lettuce.core.api.StatefulConnection;
-import io.lettuce.core.api.async.BaseRedisAsyncCommands;
-import io.lettuce.core.api.async.RedisAclAsyncCommands;
-import io.lettuce.core.api.async.RedisGeoAsyncCommands;
-import io.lettuce.core.api.async.RedisHLLAsyncCommands;
-import io.lettuce.core.api.async.RedisHashAsyncCommands;
-import io.lettuce.core.api.async.RedisKeyAsyncCommands;
-import io.lettuce.core.api.async.RedisListAsyncCommands;
-import io.lettuce.core.api.async.RedisScriptingAsyncCommands;
-import io.lettuce.core.api.async.RedisServerAsyncCommands;
-import io.lettuce.core.api.async.RedisSetAsyncCommands;
-import io.lettuce.core.api.async.RedisSortedSetAsyncCommands;
-import io.lettuce.core.api.async.RedisStringAsyncCommands;
-import io.lettuce.core.api.async.RedisTransactionalAsyncCommands;
+import io.lettuce.core.api.async.*;
 import io.lettuce.core.cluster.api.async.RedisClusterAsyncCommands;
 import io.lettuce.core.codec.Base16;
 import io.lettuce.core.codec.RedisCodec;
diff --git a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
index 42efa41acf..47006ac03d 100644
--- a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
+++ b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
@@ -21,19 +21,7 @@
 
 import io.lettuce.core.GeoArgs.Unit;
 import io.lettuce.core.api.StatefulConnection;
-import io.lettuce.core.api.reactive.BaseRedisReactiveCommands;
-import io.lettuce.core.api.reactive.RedisAclReactiveCommands;
-import io.lettuce.core.api.reactive.RedisGeoReactiveCommands;
-import io.lettuce.core.api.reactive.RedisHLLReactiveCommands;
-import io.lettuce.core.api.reactive.RedisHashReactiveCommands;
-import io.lettuce.core.api.reactive.RedisKeyReactiveCommands;
-import io.lettuce.core.api.reactive.RedisListReactiveCommands;
-import io.lettuce.core.api.reactive.RedisScriptingReactiveCommands;
-import io.lettuce.core.api.reactive.RedisServerReactiveCommands;
-import io.lettuce.core.api.reactive.RedisSetReactiveCommands;
-import io.lettuce.core.api.reactive.RedisSortedSetReactiveCommands;
-import io.lettuce.core.api.reactive.RedisStringReactiveCommands;
-import io.lettuce.core.api.reactive.RedisTransactionalReactiveCommands;
+import io.lettuce.core.api.reactive.*;
 import io.lettuce.core.cluster.api.reactive.RedisClusterReactiveCommands;
 import io.lettuce.core.codec.Base16;
 import io.lettuce.core.codec.RedisCodec;
diff --git a/src/main/java/io/lettuce/core/RedisCommandBuilder.java b/src/main/java/io/lettuce/core/RedisCommandBuilder.java
index 48b68fe1df..143fc6cd89 100644
--- a/src/main/java/io/lettuce/core/RedisCommandBuilder.java
+++ b/src/main/java/io/lettuce/core/RedisCommandBuilder.java
@@ -27,64 +27,7 @@
 import io.lettuce.core.models.stream.ClaimedMessages;
 import io.lettuce.core.models.stream.PendingMessage;
 import io.lettuce.core.models.stream.PendingMessages;
-import io.lettuce.core.output.ArrayOutput;
-import io.lettuce.core.output.BooleanListOutput;
-import io.lettuce.core.output.BooleanOutput;
-import io.lettuce.core.output.ByteArrayOutput;
-import io.lettuce.core.output.ClaimedMessagesOutput;
-import io.lettuce.core.output.CommandOutput;
-import io.lettuce.core.output.DateOutput;
-import io.lettuce.core.output.DoubleListOutput;
-import io.lettuce.core.output.DoubleOutput;
-import io.lettuce.core.output.EnumSetOutput;
-import io.lettuce.core.output.GenericMapOutput;
-import io.lettuce.core.output.GeoCoordinatesListOutput;
-import io.lettuce.core.output.GeoCoordinatesValueListOutput;
-import io.lettuce.core.output.GeoWithinListOutput;
-import io.lettuce.core.output.IntegerListOutput;
-import io.lettuce.core.output.IntegerOutput;
-import io.lettuce.core.output.KeyListOutput;
-import io.lettuce.core.output.KeyOutput;
-import io.lettuce.core.output.KeyScanOutput;
-import io.lettuce.core.output.KeyScanStreamingOutput;
-import io.lettuce.core.output.KeyStreamingChannel;
-import io.lettuce.core.output.KeyStreamingOutput;
-import io.lettuce.core.output.KeyValueListOutput;
-import io.lettuce.core.output.KeyValueListScoredValueOutput;
-import io.lettuce.core.output.KeyValueOfScoredValueOutput;
-import io.lettuce.core.output.KeyValueOutput;
-import io.lettuce.core.output.KeyValueScanStreamingOutput;
-import io.lettuce.core.output.KeyValueScoredValueOutput;
-import io.lettuce.core.output.KeyValueStreamingChannel;
-import io.lettuce.core.output.KeyValueStreamingOutput;
-import io.lettuce.core.output.KeyValueValueListOutput;
-import io.lettuce.core.output.ListOfGenericMapsOutput;
-import io.lettuce.core.output.MapOutput;
-import io.lettuce.core.output.MapScanOutput;
-import io.lettuce.core.output.NestedMultiOutput;
-import io.lettuce.core.output.ObjectOutput;
-import io.lettuce.core.output.PendingMessageListOutput;
-import io.lettuce.core.output.PendingMessagesOutput;
-import io.lettuce.core.output.ScoredValueListOutput;
-import io.lettuce.core.output.ScoredValueOutput;
-import io.lettuce.core.output.ScoredValueScanOutput;
-import io.lettuce.core.output.ScoredValueScanStreamingOutput;
-import io.lettuce.core.output.ScoredValueStreamingChannel;
-import io.lettuce.core.output.ScoredValueStreamingOutput;
-import io.lettuce.core.output.StatusOutput;
-import io.lettuce.core.output.StreamMessageListOutput;
-import io.lettuce.core.output.StreamReadOutput;
-import io.lettuce.core.output.StringListOutput;
-import io.lettuce.core.output.StringMatchResultOutput;
-import io.lettuce.core.output.StringValueListOutput;
-import io.lettuce.core.output.ValueListOutput;
-import io.lettuce.core.output.ValueOutput;
-import io.lettuce.core.output.ValueScanOutput;
-import io.lettuce.core.output.ValueScanStreamingOutput;
-import io.lettuce.core.output.ValueSetOutput;
-import io.lettuce.core.output.ValueStreamingChannel;
-import io.lettuce.core.output.ValueStreamingOutput;
-import io.lettuce.core.output.ValueValueListOutput;
+import io.lettuce.core.output.*;
 import io.lettuce.core.protocol.BaseRedisCommandBuilder;
 import io.lettuce.core.protocol.Command;
 import io.lettuce.core.protocol.CommandArgs;
@@ -100,102 +43,10 @@
 import java.util.Set;
 
 import static io.lettuce.core.internal.LettuceStrings.string;
-import static io.lettuce.core.protocol.CommandKeyword.ADDSLOTS;
-import static io.lettuce.core.protocol.CommandKeyword.ADDSLOTSRANGE;
-import static io.lettuce.core.protocol.CommandKeyword.AFTER;
-import static io.lettuce.core.protocol.CommandKeyword.AND;
-import static io.lettuce.core.protocol.CommandKeyword.BEFORE;
-import static io.lettuce.core.protocol.CommandKeyword.BUMPEPOCH;
-import static io.lettuce.core.protocol.CommandKeyword.BYLEX;
-import static io.lettuce.core.protocol.CommandKeyword.BYSCORE;
-import static io.lettuce.core.protocol.CommandKeyword.CACHING;
-import static io.lettuce.core.protocol.CommandKeyword.CAT;
-import static io.lettuce.core.protocol.CommandKeyword.CHANNELS;
-import static io.lettuce.core.protocol.CommandKeyword.CONSUMERS;
-import static io.lettuce.core.protocol.CommandKeyword.COUNT;
-import static io.lettuce.core.protocol.CommandKeyword.COUNTKEYSINSLOT;
-import static io.lettuce.core.protocol.CommandKeyword.CREATE;
-import static io.lettuce.core.protocol.CommandKeyword.DELSLOTS;
-import static io.lettuce.core.protocol.CommandKeyword.DELSLOTSRANGE;
-import static io.lettuce.core.protocol.CommandKeyword.DELUSER;
-import static io.lettuce.core.protocol.CommandKeyword.DRYRUN;
-import static io.lettuce.core.protocol.CommandKeyword.ENCODING;
-import static io.lettuce.core.protocol.CommandKeyword.FAILOVER;
-import static io.lettuce.core.protocol.CommandKeyword.FLUSH;
-import static io.lettuce.core.protocol.CommandKeyword.FLUSHSLOTS;
-import static io.lettuce.core.protocol.CommandKeyword.FORCE;
-import static io.lettuce.core.protocol.CommandKeyword.FORGET;
-import static io.lettuce.core.protocol.CommandKeyword.FREQ;
-import static io.lettuce.core.protocol.CommandKeyword.GENPASS;
-import static io.lettuce.core.protocol.CommandKeyword.GETKEYSINSLOT;
-import static io.lettuce.core.protocol.CommandKeyword.GETNAME;
-import static io.lettuce.core.protocol.CommandKeyword.GETREDIR;
-import static io.lettuce.core.protocol.CommandKeyword.GETUSER;
-import static io.lettuce.core.protocol.CommandKeyword.GROUPS;
-import static io.lettuce.core.protocol.CommandKeyword.HARD;
-import static io.lettuce.core.protocol.CommandKeyword.HTSTATS;
-import static io.lettuce.core.protocol.CommandKeyword.ID;
-import static io.lettuce.core.protocol.CommandKeyword.IDLETIME;
-import static io.lettuce.core.protocol.CommandKeyword.IMPORTING;
-import static io.lettuce.core.protocol.CommandKeyword.KEYSLOT;
-import static io.lettuce.core.protocol.CommandKeyword.KILL;
-import static io.lettuce.core.protocol.CommandKeyword.LEN;
-import static io.lettuce.core.protocol.CommandKeyword.LIMIT;
-import static io.lettuce.core.protocol.CommandKeyword.LIST;
-import static io.lettuce.core.protocol.CommandKeyword.LOAD;
-import static io.lettuce.core.protocol.CommandKeyword.LOG;
-import static io.lettuce.core.protocol.CommandKeyword.MAXLEN;
-import static io.lettuce.core.protocol.CommandKeyword.MEET;
-import static io.lettuce.core.protocol.CommandKeyword.MIGRATING;
-import static io.lettuce.core.protocol.CommandKeyword.NO;
-import static io.lettuce.core.protocol.CommandKeyword.NODE;
-import static io.lettuce.core.protocol.CommandKeyword.NODES;
-import static io.lettuce.core.protocol.CommandKeyword.NOSAVE;
-import static io.lettuce.core.protocol.CommandKeyword.NOT;
-import static io.lettuce.core.protocol.CommandKeyword.NOVALUES;
-import static io.lettuce.core.protocol.CommandKeyword.NUMPAT;
-import static io.lettuce.core.protocol.CommandKeyword.NUMSUB;
-import static io.lettuce.core.protocol.CommandKeyword.OFF;
-import static io.lettuce.core.protocol.CommandKeyword.ON;
-import static io.lettuce.core.protocol.CommandKeyword.ONE;
-import static io.lettuce.core.protocol.CommandKeyword.OR;
-import static io.lettuce.core.protocol.CommandKeyword.PAUSE;
-import static io.lettuce.core.protocol.CommandKeyword.REFCOUNT;
-import static io.lettuce.core.protocol.CommandKeyword.RELOAD;
-import static io.lettuce.core.protocol.CommandKeyword.REPLACE;
-import static io.lettuce.core.protocol.CommandKeyword.REPLICAS;
-import static io.lettuce.core.protocol.CommandKeyword.REPLICATE;
-import static io.lettuce.core.protocol.CommandKeyword.RESET;
-import static io.lettuce.core.protocol.CommandKeyword.RESETSTAT;
-import static io.lettuce.core.protocol.CommandKeyword.RESTART;
-import static io.lettuce.core.protocol.CommandKeyword.REV;
-import static io.lettuce.core.protocol.CommandKeyword.REWRITE;
-import static io.lettuce.core.protocol.CommandKeyword.SAVECONFIG;
-import static io.lettuce.core.protocol.CommandKeyword.SEGFAULT;
-import static io.lettuce.core.protocol.CommandKeyword.SETINFO;
-import static io.lettuce.core.protocol.CommandKeyword.SETNAME;
-import static io.lettuce.core.protocol.CommandKeyword.SETSLOT;
-import static io.lettuce.core.protocol.CommandKeyword.SETUSER;
-import static io.lettuce.core.protocol.CommandKeyword.SHARDCHANNELS;
-import static io.lettuce.core.protocol.CommandKeyword.SHARDNUMSUB;
-import static io.lettuce.core.protocol.CommandKeyword.SHARDS;
-import static io.lettuce.core.protocol.CommandKeyword.SLAVES;
-import static io.lettuce.core.protocol.CommandKeyword.SLOTS;
-import static io.lettuce.core.protocol.CommandKeyword.SOFT;
-import static io.lettuce.core.protocol.CommandKeyword.STABLE;
-import static io.lettuce.core.protocol.CommandKeyword.STREAM;
-import static io.lettuce.core.protocol.CommandKeyword.TAKEOVER;
-import static io.lettuce.core.protocol.CommandKeyword.TRACKING;
-import static io.lettuce.core.protocol.CommandKeyword.UNBLOCK;
-import static io.lettuce.core.protocol.CommandKeyword.USAGE;
-import static io.lettuce.core.protocol.CommandKeyword.USERS;
-import static io.lettuce.core.protocol.CommandKeyword.WHOAMI;
-import static io.lettuce.core.protocol.CommandKeyword.WITHSCORE;
-import static io.lettuce.core.protocol.CommandKeyword.WITHSCORES;
-import static io.lettuce.core.protocol.CommandKeyword.WITHVALUES;
-import static io.lettuce.core.protocol.CommandKeyword.XOR;
-import static io.lettuce.core.protocol.CommandKeyword.YES;
+import static io.lettuce.core.protocol.CommandKeyword.*;
 import static io.lettuce.core.protocol.CommandType.*;
+import static io.lettuce.core.protocol.CommandType.COPY;
+import static io.lettuce.core.protocol.CommandType.SAVE;
 
 /**
  * @param <K>
diff --git a/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java
index b5253b4e9d..cceffb8a71 100644
--- a/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java
@@ -19,25 +19,15 @@
  */
 package io.lettuce.core.api.async;
 
-import io.lettuce.core.CopyArgs;
-import io.lettuce.core.ExpireArgs;
-import io.lettuce.core.KeyScanArgs;
-import io.lettuce.core.KeyScanCursor;
-import io.lettuce.core.MigrateArgs;
-import io.lettuce.core.RedisFuture;
-import io.lettuce.core.RestoreArgs;
-import io.lettuce.core.ScanArgs;
-import io.lettuce.core.ScanCursor;
-import io.lettuce.core.SortArgs;
-import io.lettuce.core.StreamScanCursor;
-import io.lettuce.core.output.KeyStreamingChannel;
-import io.lettuce.core.output.ValueStreamingChannel;
-
 import java.time.Duration;
 import java.time.Instant;
 import java.util.Date;
 import java.util.List;
 
+import io.lettuce.core.*;
+import io.lettuce.core.output.KeyStreamingChannel;
+import io.lettuce.core.output.ValueStreamingChannel;
+
 /**
  * Asynchronous executed commands for Keys (Key manipulation/querying).
  *
diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java
index 398fcf1f65..d09dd4f39a 100644
--- a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java
+++ b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java
@@ -19,6 +19,11 @@
  */
 package io.lettuce.core.cluster.api.sync;
 
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+
 import io.lettuce.core.CopyArgs;
 import io.lettuce.core.ExpireArgs;
 import io.lettuce.core.KeyScanArgs;
@@ -32,11 +37,6 @@
 import io.lettuce.core.output.KeyStreamingChannel;
 import io.lettuce.core.output.ValueStreamingChannel;
 
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Date;
-import java.util.List;
-
 /**
  * Synchronous executed commands on a node selection for Keys (Key manipulation/querying).
  *
diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
index 72aa35c614..87c92abb2e 100644
--- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
+++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
@@ -20,19 +20,11 @@
 
 package io.lettuce.core.api.coroutines
 
-import io.lettuce.core.CopyArgs
-import io.lettuce.core.ExperimentalLettuceCoroutinesApi
-import io.lettuce.core.ExpireArgs
-import io.lettuce.core.KeyScanCursor
-import io.lettuce.core.MigrateArgs
-import io.lettuce.core.RestoreArgs
-import io.lettuce.core.ScanArgs
-import io.lettuce.core.ScanCursor
-import io.lettuce.core.SortArgs
+import io.lettuce.core.*
 import kotlinx.coroutines.flow.Flow
 import java.time.Duration
 import java.time.Instant
-import java.util.Date
+import java.util.*
 
 /**
  * Coroutine executed commands for Keys (Key manipulation/querying).
diff --git a/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
index 2188595fc1..45d7e3840d 100644
--- a/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
@@ -19,6 +19,24 @@
  */
 package io.lettuce.core.commands;
 
+import static org.assertj.core.api.Assertions.*;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.extension.ExtendWith;
+
 import io.lettuce.core.CopyArgs;
 import io.lettuce.core.ExpireArgs;
 import io.lettuce.core.KeyScanArgs;
@@ -32,23 +50,6 @@
 import io.lettuce.test.LettuceExtension;
 import io.lettuce.test.ListStreamingAdapter;
 import io.lettuce.test.condition.EnabledOnCommand;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestInstance;
-import org.junit.jupiter.api.extension.ExtendWith;
-
-import javax.inject.Inject;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 /**
  * Integration tests for {@link io.lettuce.core.api.sync.RedisKeyCommands}.

From 667afc169ccc2cae262029ee060c8e17f240a511 Mon Sep 17 00:00:00 2001
From: Tihomir Mateev <tihomir.mateev@gmail.com>
Date: Thu, 25 Apr 2024 15:51:36 +0300
Subject: [PATCH 7/7] Polishin : Copyright change not needed

---
 .../lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
index 87c92abb2e..6d3f92bcad 100644
--- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
+++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017-Present, Redis Ltd. and Contributors
+ * Copyright 2020-Present, Redis Ltd. and Contributors
  * All rights reserved.
  *
  * Licensed under the MIT License.