jsonType(byte[] key, JsonPath path);
+
+ /**
+ * {@code JSON.SET} command arguments for {@code NX}, {@code XX}.
+ */
+ enum JsonSetOption {
+
+ /**
+ * Do not set any additional command argument.
+ */
+ UPSERT,
+
+ /**
+ * {@code NX}
+ */
+ IF_PATH_NOT_EXISTS,
+
+ /**
+ * {@code XX}
+ */
+ IF_PATH_EXISTS;
+
+ /**
+ * Do not set any additional command argument.
+ *
+ * @return {@link JsonSetOption#UPSERT}
+ */
+ public static JsonSetOption upsert() {
+ return UPSERT;
+ }
+
+ /**
+ * {@code NX}
+ *
+ * @return {@link JsonSetOption#IF_PATH_NOT_EXISTS}
+ */
+ public static JsonSetOption ifPathNotExists() {
+ return IF_PATH_NOT_EXISTS;
+ }
+
+ /**
+ * {@code XX}
+ *
+ * @return {@link JsonSetOption#IF_PATH_EXISTS}
+ */
+ public static JsonSetOption ifPathExists() {
+ return IF_PATH_EXISTS;
+ }
+
+ }
+
+ /**
+ * Arguments for {@code JSON.MSET} command.
+ *
+ * @param key the key, must not be {@literal null}.
+ * @param path the JSON path, must not be {@literal null}.
+ * @param value the value to set.
+ * @since 4.3
+ */
+ record JsonMSetArgs(byte[] key, JsonPath path, String value) {
+
+ public JsonMSetArgs {
+ Assert.notNull(key, "Key must not be null");
+ Assert.notNull(path, "Path must not be null");
+ Assert.notNull(value, "Value must not be null");
+ }
+
+ public JsonMSetArgs(byte[] key, String value) {
+ this(key, JsonPath.root(), value);
+ }
+
+ }
+
+ enum JsonType {
+
+ NULL, STRING, NUMBER, BOOLEAN, OBJECT, ARRAY;
+
+ }
+
+}
diff --git a/src/main/java/org/springframework/data/redis/core/JsonOperations.java b/src/main/java/org/springframework/data/redis/core/JsonOperations.java
new file mode 100644
index 0000000000..7cf11f7ba1
--- /dev/null
+++ b/src/main/java/org/springframework/data/redis/core/JsonOperations.java
@@ -0,0 +1,608 @@
+/*
+ * Copyright 2026-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.redis.core;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.NullUnmarked;
+import org.jspecify.annotations.Nullable;
+
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.data.domain.Range;
+import org.springframework.data.redis.core.json.JsonPath;
+import org.springframework.util.Assert;
+
+/**
+ * Redis JSON specific operations, working on JSON documents stored in Redis.
+ *
+ * This interface provides high-level operations for manipulating JSON data structures
+ * using the Redis JSON module. Operations include reading and writing JSON values,
+ * array manipulation, string operations, and numeric operations at specific JSON paths.
+ *
+ * JSON paths follow the JSONPath syntax, where {@code $} represents the root element.
+ * Use {@link JsonPath} to construct paths programmatically.
+ *
+ * @author Yordan Tsintsov
+ * @see Redis JSON Documentation
+ * @since 4.3
+ */
+@NullUnmarked
+public interface JsonOperations {
+
+ /**
+ * Append {@code values} to the JSON array at {@code path} in the document stored at {@code key}.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @param values can be {@literal null}.
+ * @return a list where each element contains the new array length at matching paths,
+ * or {@literal null} if the path does not exist or is not an array.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.ARRAPPEND
+ */
+ List<@Nullable Long> arrayAppend(@NonNull K key, @NonNull JsonPath path, Object... values);
+
+ /**
+ * Search for the first occurrence of {@code value} in the JSON array at {@code path}.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @param value can be {@literal null}.
+ * @return a list where each element contains the index of the first occurrence of the value,
+ * {@literal -1} if not found, or {@literal null} if the path does not exist or is not an array.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.ARRINDEX
+ */
+ default List<@Nullable Long> arrayIndex(@NonNull K key, @NonNull JsonPath path, Object value) {
+
+ Assert.notNull(key, "Key must not be null");
+ Assert.notNull(path, "Path must not be null");
+
+ return arrayIndex(key, path, value, Range.unbounded());
+ }
+
+ /**
+ * Search for the first occurrence of {@code value} in the JSON array at {@code path},
+ * within the {@code range}.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @param value can be {@literal null}.
+ * @param range must not be {@literal null}.
+ * @return a list where each element contains the index of the first occurrence of the value,
+ * {@literal -1} if not found, or {@literal null} if the path does not exist or is not an array.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.ARRINDEX
+ */
+ List<@Nullable Long> arrayIndex(@NonNull K key, @NonNull JsonPath path, Object value, @NonNull Range<@NonNull Long> range);
+
+ /**
+ * Insert {@code values} into the JSON array at {@code path} before the element at {@code index}.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @param index the position to insert before. Negative values count from the end of the array.
+ * @param values can be {@literal null}.
+ * @return a list where each element contains the new array length at matching paths,
+ * or {@literal null} if the path does not exist or is not an array.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.ARRINSERT
+ */
+ List<@Nullable Long> arrayInsert(@NonNull K key, @NonNull JsonPath path, int index, Object... values);
+
+ /**
+ * Get the length of the JSON array at {@code path}.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @return a list where each element contains the array length at matching paths,
+ * or {@literal null} if the path does not exist or is not an array.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.ARRLEN
+ */
+ List<@Nullable Long> arrayLength(@NonNull K key, @NonNull JsonPath path);
+
+ /**
+ * Remove and return the last element from the JSON array at {@code path}.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @param clazz must not be {@literal null}.
+ * @return a list where each element contains the popped value at matching paths,
+ * or {@literal null} if the path does not exist, is not an array, or the array is empty.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.ARRPOP
+ */
+ default List<@Nullable T> arrayPop(@NonNull K key, @NonNull JsonPath path, @NonNull Class clazz) {
+
+ Assert.notNull(key, "Key must not be null");
+ Assert.notNull(path, "Path must not be null");
+ Assert.notNull(clazz, "Class must not be null");
+
+ return arrayPop(key, path, clazz, -1);
+ }
+
+ /**
+ * Remove and return the element at {@code index} from the JSON array at {@code path}.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @param clazz must not be {@literal null}.
+ * @param index the position to pop from. Negative values count from the end of the array.
+ * @return a list where each element contains the popped value at matching paths,
+ * or {@literal null} if the path does not exist, is not an array, or index is out of bounds.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.ARRPOP
+ */
+ List<@Nullable T> arrayPop(@NonNull K key, @NonNull JsonPath path, @NonNull Class clazz, int index);
+
+ /**
+ * Remove and return the last element from the JSON array at {@code path}. Use this variant when the target value is a nested object.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @param typeRef must not be {@literal null}.
+ * @return a list where each element contains the popped value at matching paths,
+ * or {@literal null} if the path does not exist, is not an array, or index is out of bounds.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.ARRPOP
+ */
+ default List<@Nullable T> arrayPop(@NonNull K key, @NonNull JsonPath path, @NonNull ParameterizedTypeReference<@NonNull T> typeRef) {
+
+ Assert.notNull(key, "Key must not be null");
+ Assert.notNull(path, "Path must not be null");
+ Assert.notNull(typeRef, "TypeReference must not be null");
+
+ return arrayPop(key, path, typeRef, -1);
+ }
+
+ /**
+ * Remove and return the element at {@code index} from the JSON array at {@code path}. Use this variant when the target value is a nested object.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @param typeRef must not be {@literal null}.
+ * @param index the position to pop from. Negative values count from the end of the array.
+ * @return a list where each element contains the popped value at matching paths,
+ * or {@literal null} if the path does not exist, is not an array, or index is out of bounds.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.ARRPOP
+ */
+ List<@Nullable T> arrayPop(@NonNull K key, @NonNull JsonPath path, @NonNull ParameterizedTypeReference<@NonNull T> typeRef, int index);
+
+ /**
+ * Trim the JSON array at {@code path} to contain only elements within the range [{@code start}, {@code stop}].
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @param start the start index (inclusive). Negative values count from the end of the array.
+ * @param stop the stop index (inclusive). Negative values count from the end of the array.
+ * @return a list where each element contains the new array length at matching paths,
+ * or {@literal null} if the path does not exist or is not an array.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.ARRTRIM
+ */
+ List<@Nullable Long> arrayTrim(@NonNull K key, @NonNull JsonPath path, int start, int stop);
+
+ /**
+ * Clear container values (arrays/objects) and set numeric values to {@literal 0} at the root path
+ * of the document stored at {@code key}.
+ *
+ * @param key must not be {@literal null}.
+ * @return the number of values cleared. {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.CLEAR
+ */
+ default Long clear(@NonNull K key) {
+
+ Assert.notNull(key, "Key must not be null");
+
+ return clear(key, JsonPath.root());
+ }
+
+ /**
+ * Clear container values (arrays/objects) and set numeric values to {@literal 0} at {@code path}
+ * in the document stored at {@code key}.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @return the number of values cleared. {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.CLEAR
+ */
+ Long clear(@NonNull K key, @NonNull JsonPath path);
+
+ /**
+ * Delete the entire JSON document stored at {@code key}.
+ *
+ * @param key must not be {@literal null}.
+ * @return the number of paths deleted (0 or 1). {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.DEL
+ */
+ default Long delete(@NonNull K key) {
+
+ Assert.notNull(key, "Key must not be null");
+
+ return delete(key, JsonPath.root());
+ }
+
+ /**
+ * Delete the JSON value at {@code path} in the document stored at {@code key}.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @return the number of paths deleted. {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.DEL
+ */
+ Long delete(@NonNull K key, @NonNull JsonPath path);
+
+ /**
+ * Get the entire JSON document stored at {@code key}.
+ *
+ * @param key must not be {@literal null}.
+ * @param clazz must not be {@literal null}.
+ * @return the deserialized JSON document, or {@literal null} if the key does not exist.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.GET
+ */
+ default @Nullable T get(@NonNull K key, @NonNull Class clazz) {
+
+ Assert.notNull(key, "Key must not be null");
+ Assert.notNull(clazz, "Class must not be null");
+
+ List<@Nullable T> result = get(key, clazz, JsonPath.root());
+
+ return result.isEmpty() ? null : result.getFirst();
+ }
+
+ /**
+ * Get the JSON values at multiple {@code paths} in the document stored at {@code key}.
+ *
+ * @param key must not be {@literal null}.
+ * @param clazz must not be {@literal null}.
+ * @param paths must not be {@literal null}.
+ * @return a list where each element contains the value at matching paths,
+ * or {@literal null} if the path does not exist.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.GET
+ */
+ List<@Nullable T> get(@NonNull K key, @NonNull Class clazz, @NonNull JsonPath @NonNull... paths);
+
+ /**
+ * Get the entire JSON document stored at {@code key}. Use this variant when the target value is a nested object.
+ *
+ * @param key must not be {@literal null}.
+ * @param typeRef must not be {@literal null}.
+ * @return the deserialized JSON document, or {@literal null} if the key does not exist.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.GET
+ */
+ default @Nullable T get(@NonNull K key, @NonNull ParameterizedTypeReference<@NonNull T> typeRef) {
+
+ Assert.notNull(key, "Key must not be null");
+ Assert.notNull(typeRef, "Type reference must not be null");
+
+ List<@Nullable T> result = get(key, typeRef, JsonPath.root());
+
+ return result.isEmpty() ? null : result.getFirst();
+ }
+
+ /**
+ * Get the JSON values at multiple {@code paths} in the document stored at {@code key}. Use this variant when the target value is a nested object.
+ *
+ * @param key must not be {@literal null}.
+ * @param typeRef must not be {@literal null}.
+ * @param paths must not be {@literal null}.
+ * @return a list where each element contains the value at matching paths,
+ * or {@literal null} if the path does not exist.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.GET
+ */
+ List<@Nullable T> get(@NonNull K key, @NonNull ParameterizedTypeReference<@NonNull T> typeRef, @NonNull JsonPath @NonNull... paths);
+
+ /**
+ * Increment the numeric value at {@code path} by {@code number}.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @param number must not be {@literal null}.
+ * @return a list where each element contains the new value at matching paths,
+ * or {@literal null} if the path does not exist or is not a number.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.NUMINCRBY
+ */
+ List<@Nullable Number> increment(@NonNull K key, @NonNull JsonPath path, @NonNull Number number);
+
+ /**
+ * Merge {@code value} into the root of the JSON document stored at {@code key}.
+ *
+ * Merging follows RFC 7396 semantics: existing object keys are updated or added,
+ * and values set to {@literal null} are deleted.
+ *
+ * @param key must not be {@literal null}.
+ * @param value must not be {@literal null}.
+ * @return {@literal true} if the merge was successful, {@literal false} otherwise.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.MERGE
+ */
+ default Boolean merge(@NonNull K key, Object value) {
+
+ Assert.notNull(key, "Key must not be null");
+
+ return merge(key, JsonPath.root(), value);
+ }
+
+ /**
+ * Merge {@code value} into the JSON document at {@code path} stored at {@code key}.
+ *
+ * Merging follows RFC 7396 semantics: existing object keys are updated or added,
+ * and values set to {@literal null} are deleted.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @param value can be {@literal null}.
+ * @return {@literal true} if the merge was successful, {@literal false} otherwise.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.MERGE
+ */
+ Boolean merge(@NonNull K key, @NonNull JsonPath path, Object value);
+
+ /**
+ * Get the JSON documents stored at multiple {@code keys}.
+ *
+ * @param keys must not be {@literal null}.
+ * @param clazz must not be {@literal null}.
+ * @return a list of values at the root path for each key, with {@literal null} for keys that do not exist.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.MGET
+ */
+ default List<@Nullable T> multiGet(@NonNull Collection keys, @NonNull Class clazz) {
+
+ Assert.notEmpty(keys, "Keys must not be null or empty");
+ Assert.notNull(clazz, "Class must not be null");
+
+ return multiGet(keys, clazz, JsonPath.root());
+ }
+
+ /**
+ * Get the JSON values at {@code path} from documents stored at multiple {@code keys}.
+ *
+ * @param keys must not be {@literal null}.
+ * @param clazz must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @return a list of values at the path for each key, with {@literal null} for keys where the path does not exist.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.MGET
+ */
+ List<@Nullable T> multiGet(@NonNull Collection keys, @NonNull Class clazz, @NonNull JsonPath path);
+
+ /**
+ * Get the JSON documents stored at multiple {@code keys}. Use this variant when the target value is a nested object.
+ *
+ * @param keys must not be {@literal null}.
+ * @param typeRef must not be {@literal null}.
+ * @return a list of values at the root path for each key, with {@literal null} for keys that do not exist.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.MGET
+ */
+ default List<@Nullable T> multiGet(@NonNull Collection keys, @NonNull ParameterizedTypeReference<@NonNull T> typeRef) {
+
+ Assert.notEmpty(keys, "Keys must not be null or empty");
+ Assert.notNull(typeRef, "TypeReference must not be null");
+
+ return multiGet(keys, typeRef, JsonPath.root());
+ }
+
+ /**
+ * Get the JSON values at {@code path} from documents stored at multiple {@code keys}. Use this variant when the target value is a nested object.
+ *
+ * @param keys must not be {@literal null}.
+ * @param typeRef must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @return a list of values at the path for each key, with {@literal null} for keys where the path does not exist.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.MGET
+ */
+ List<@Nullable T> multiGet(@NonNull Collection keys, @NonNull ParameterizedTypeReference<@NonNull T> typeRef, @NonNull JsonPath path);
+
+ /**
+ * Set JSON values at the specified keys and paths atomically.
+ *
+ * @param args must not be {@literal null}.
+ * @return {@literal true} if all values were set successfully, {@literal false} otherwise.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.MSET
+ */
+ Boolean multiSet(@NonNull List> args);
+
+ /**
+ * Set the JSON {@code value} at the root path of the document stored at {@code key}.
+ *
+ * @param key must not be {@literal null}.
+ * @param value can be {@literal null}.
+ * @return {@literal true} if the value was set successfully, {@literal false} otherwise.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.SET
+ */
+ default Boolean set(@NonNull K key, Object value) {
+
+ Assert.notNull(key, "Key must not be null");
+
+ return set(key, JsonPath.root(), value);
+ }
+
+ /**
+ * Set the JSON {@code value} at {@code path} in the document stored at {@code key}.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @param value can be {@literal null}.
+ * @return {@literal true} if the value was set successfully, {@literal false} otherwise.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.SET
+ */
+ Boolean set(@NonNull K key, @NonNull JsonPath path, Object value);
+
+ /**
+ * Set the JSON {@code value} at the root path only if the key does not already exist.
+ *
+ * @param key must not be {@literal null}.
+ * @param value can be {@literal null}.
+ * @return {@literal true} if the value was set, {@literal false} if the key already exists.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.SET
+ */
+ Boolean setIfAbsent(@NonNull K key, Object value);
+
+ /**
+ * Set the JSON {@code value} at the root path only if the key already exists.
+ *
+ * @param key must not be {@literal null}.
+ * @param value can be {@literal null}.
+ * @return {@literal true} if the value was set, {@literal false} if the key does not exist.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.SET
+ */
+ Boolean setIfPresent(@NonNull K key, Object value);
+
+ /**
+ * Set the JSON {@code value} at {@code path} only if the path does not already exist.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @param value can be {@literal null}.
+ * @return {@literal true} if the value was set, {@literal false} if the path already exists.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.SET
+ */
+ Boolean setIfPathAbsent(@NonNull K key, @NonNull JsonPath path, Object value);
+
+ /**
+ * Set the JSON {@code value} at {@code path} only if the path already exists.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @param value can be {@literal null}.
+ * @return {@literal true} if the value was set, {@literal false} if the path does not exist.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.SET
+ */
+ Boolean setIfPathPresent(@NonNull K key, @NonNull JsonPath path, Object value);
+
+ /**
+ * Append {@code value} to the JSON string at {@code path}.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @param value must not be {@literal null}.
+ * @return a list where each element contains the new string length at matching paths,
+ * or {@literal null} if the path does not exist or is not a string.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.STRAPPEND
+ */
+ List<@Nullable Long> stringAppend(@NonNull K key, @NonNull JsonPath path, @NonNull String value);
+
+ /**
+ * Get the length of the JSON string at {@code path}.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @return a list where each element contains the string length at matching paths,
+ * or {@literal null} if the path does not exist or is not a string.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.STRLEN
+ */
+ List<@Nullable Long> stringLength(@NonNull K key, @NonNull JsonPath path);
+
+ /**
+ * Toggle the JSON boolean value at {@code path}.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @return a list where each element contains the new boolean value at matching paths,
+ * or {@literal null} if the path does not exist or is not a boolean.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.TOGGLE
+ */
+ List<@Nullable Boolean> toggle(@NonNull K key, @NonNull JsonPath path);
+
+ /**
+ * Get the JSON type at the root path of the document stored at {@code key}.
+ *
+ * @param key must not be {@literal null}.
+ * @return a list containing the type at the root path. Returns an empty list if the key does not exist.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.TYPE
+ */
+ default List> type(@NonNull K key) {
+
+ Assert.notNull(key, "Key must not be null");
+
+ return type(key, JsonPath.root());
+ }
+
+ /**
+ * Get the JSON type at {@code path} in the document stored at {@code key}.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @return a list where each element contains the type at matching paths.
+ * Returns an empty list if the path does not exist.
+ * {@literal null} when used in pipeline / transaction.
+ * @see Redis Documentation: JSON.TYPE
+ */
+ List> type(@NonNull K key, @NonNull JsonPath path);
+
+ /**
+ * @return the underlying {@link RedisOperations} used to execute commands.
+ */
+ @NonNull
+ RedisOperations getOperations();
+
+ /**
+ * Arguments for {@link #multiSet(List)} operation.
+ *
+ * @param key the key, must not be {@literal null}.
+ * @param path the JSON path, must not be {@literal null}.
+ * @param value can be {@literal null}.
+ */
+ record JsonMultiSetArgs(@NonNull K key, @NonNull JsonPath path, Object value) {
+
+ /**
+ * Creates a new {@link JsonMultiSetArgs} with validation.
+ *
+ * @param key must not be {@literal null}.
+ * @param path must not be {@literal null}.
+ * @param value can be {@literal null}.
+ */
+ public JsonMultiSetArgs {
+ Assert.notNull(key, "Key must not be null");
+ Assert.notNull(path, "Path must not be null");
+ }
+
+ /**
+ * Creates a new {@link JsonMultiSetArgs} for the root path.
+ *
+ * @param key must not be {@literal null}.
+ * @param value must not be {@literal null}.
+ */
+ public JsonMultiSetArgs(K key, Object value) {
+ this(key, JsonPath.root(), value);
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/springframework/data/redis/core/json/JsonPath.java b/src/main/java/org/springframework/data/redis/core/json/JsonPath.java
new file mode 100644
index 0000000000..9c7d0f5405
--- /dev/null
+++ b/src/main/java/org/springframework/data/redis/core/json/JsonPath.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2026-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.redis.core.json;
+
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+import org.springframework.util.Assert;
+
+import java.util.Objects;
+
+/**
+ * Immutable value object representing a JSONPath expression for use with Redis JSON operations.
+ *
+ * @author Yordan Tsintsov
+ * @see Redis JSONPath Documentation
+ * @since 4.3
+ */
+@NullMarked
+public final class JsonPath {
+
+ private static final String ROOT_PATH = "$";
+ private static final JsonPath ROOT = new JsonPath(ROOT_PATH);
+
+ private final String path;
+
+ private JsonPath(String path) {
+ this.path = path;
+ }
+
+ /**
+ * Returns the root path {@code $}.
+ *
+ * @return the root {@link JsonPath}
+ */
+ public static JsonPath root() {
+ return ROOT;
+ }
+
+ /**
+ * Creates a {@link JsonPath} from a raw path string.
+ *
+ * @param path the path expression, must not be empty
+ * @return a new {@link JsonPath}
+ */
+ public static JsonPath of(String path) {
+
+ Assert.hasText(path, "Path must not be empty");
+
+ return new JsonPath(path);
+ }
+
+ /**
+ * Returns the path as a string.
+ *
+ * @return the path expression
+ */
+ public String getPath() {
+ return path;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ JsonPath jsonPath = (JsonPath) o;
+ return Objects.equals(path, jsonPath.path);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(path);
+ }
+
+ @Override
+ public String toString() {
+ return path;
+ }
+
+}