> keys);
/**
- * Find all keys matching the given {@literal pattern}.
- * It is recommended to use {@link #scan(ScanOptions)} to iterate over the keyspace as {@link #keys(ByteBuffer)} is a
- * non-interruptible and expensive Redis operation.
+ * Retrieve all keys matching the given pattern via {@code KEYS} command.
+ *
+ * IMPORTANT: This command is non-interruptible and scans the entire keyspace which may cause
+ * performance issues. Consider {@link #scan(ScanOptions)} for large datasets.
*
* @param pattern must not be {@literal null}.
* @return
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java
index 4319dd8705..e79cf2c0f8 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java
@@ -122,7 +122,10 @@ default Boolean exists(byte[] key) {
Long touch(byte[]... keys);
/**
- * Find all keys matching the given {@code pattern}.
+ * Retrieve all keys matching the given pattern.
+ *
+ * IMPORTANT: The {@literal KEYS} command is non-interruptible and scans the entire keyspace which
+ * may cause performance issues. Consider {@link #scan(ScanOptions)} for large datasets.
*
* @param pattern must not be {@literal null}.
* @return empty {@link Set} if no match found. {@literal null} when used in pipeline / transaction.
diff --git a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java
index dfcc585130..dd483bdf89 100644
--- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java
+++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java
@@ -185,7 +185,10 @@ interface StringTuple extends Tuple {
Long touch(String... keys);
/**
- * Find all keys matching the given {@code pattern}.
+ * Retrieve all keys matching the given pattern via {@code KEYS} command.
+ *
+ * IMPORTANT: This command is non-interruptible and scans the entire keyspace which may cause
+ * performance issues. Consider {@link #scan(ScanOptions)} for large datasets.
*
* @param pattern must not be {@literal null}.
* @return
diff --git a/src/main/java/org/springframework/data/redis/core/ClusterOperations.java b/src/main/java/org/springframework/data/redis/core/ClusterOperations.java
index f96c8c0d39..01ebb31246 100644
--- a/src/main/java/org/springframework/data/redis/core/ClusterOperations.java
+++ b/src/main/java/org/springframework/data/redis/core/ClusterOperations.java
@@ -38,7 +38,10 @@
public interface ClusterOperations {
/**
- * Get all keys located at given node.
+ * Retrieve all keys located at given node matching the given pattern.
+ *
+ * IMPORTANT: The {@literal KEYS} command is non-interruptible and scans the entire keyspace which
+ * may cause performance issues.
*
* @param node must not be {@literal null}.
* @param pattern
diff --git a/src/main/java/org/springframework/data/redis/core/ReactiveRedisOperations.java b/src/main/java/org/springframework/data/redis/core/ReactiveRedisOperations.java
index 686277f0df..55f126b636 100644
--- a/src/main/java/org/springframework/data/redis/core/ReactiveRedisOperations.java
+++ b/src/main/java/org/springframework/data/redis/core/ReactiveRedisOperations.java
@@ -264,9 +264,10 @@ default Mono>> listenToPatternLater(String...
Mono type(K key);
/**
- * Find all keys matching the given {@code pattern}.
- * IMPORTANT: It is recommended to use {@link #scan()} to iterate over the keyspace as
- * {@link #keys(Object)} is a non-interruptible and expensive Redis operation.
+ * Retrieve all keys matching the given pattern via {@code KEYS} command.
+ *
+ * IMPORTANT: This command is non-interruptible and scans the entire keyspace which may cause
+ * performance issues. Consider {@link #scan(ScanOptions)} for large datasets.
*
* @param pattern must not be {@literal null}.
* @return the {@link Flux} emitting matching keys one by one.
diff --git a/src/main/java/org/springframework/data/redis/core/RedisOperations.java b/src/main/java/org/springframework/data/redis/core/RedisOperations.java
index 4ea682d900..18783d2c92 100644
--- a/src/main/java/org/springframework/data/redis/core/RedisOperations.java
+++ b/src/main/java/org/springframework/data/redis/core/RedisOperations.java
@@ -262,11 +262,14 @@ T execute(RedisScript script, RedisSerializer> argsSerializer, RedisSer
DataType type(K key);
/**
- * Find all keys matching the given {@code pattern}.
- *
- * @param pattern must not be {@literal null}.
- * @return {@literal null} when used in pipeline / transaction.
- * @see Redis Documentation: KEYS
+ * Retrieve all keys matching the given pattern via {@code KEYS} command.
+ *
+ * IMPORTANT: This command is non-interruptible and scans the entire keyspace which may cause
+ * performance issues. Consider {@link #scan(ScanOptions)} for large datasets.
+ *
+ * @param pattern key pattern
+ * @return set of matching keys, or {@literal null} when used in pipeline / transaction
+ * @see Redis KEYS command
*/
@Nullable
Set keys(K pattern);
diff --git a/src/main/java/org/springframework/data/redis/hash/Jackson2HashMapper.java b/src/main/java/org/springframework/data/redis/hash/Jackson2HashMapper.java
index 8788c41ba4..d16d263d69 100644
--- a/src/main/java/org/springframework/data/redis/hash/Jackson2HashMapper.java
+++ b/src/main/java/org/springframework/data/redis/hash/Jackson2HashMapper.java
@@ -143,7 +143,9 @@
* @author Mark Paluch
* @author John Blum
* @since 1.8
+ * @deprecated since 4.0
*/
+@Deprecated(since = "4.0", forRemoval = true)
public class Jackson2HashMapper implements HashMapper {
private static final boolean SOURCE_VERSION_PRESENT =
diff --git a/src/main/java/org/springframework/data/redis/hash/Jackson3HashMapper.java b/src/main/java/org/springframework/data/redis/hash/Jackson3HashMapper.java
new file mode 100644
index 0000000000..20cd8d906f
--- /dev/null
+++ b/src/main/java/org/springframework/data/redis/hash/Jackson3HashMapper.java
@@ -0,0 +1,577 @@
+/*
+ * Copyright 2016-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.hash;
+
+import tools.jackson.core.JacksonException;
+import tools.jackson.core.JsonGenerator;
+import tools.jackson.core.JsonParser;
+import tools.jackson.core.Version;
+import tools.jackson.databind.DefaultTyping;
+import tools.jackson.databind.DeserializationContext;
+import tools.jackson.databind.DeserializationFeature;
+import tools.jackson.databind.JacksonModule;
+import tools.jackson.databind.JsonNode;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.SerializationContext;
+import tools.jackson.databind.ValueDeserializer;
+import tools.jackson.databind.ValueSerializer;
+import tools.jackson.databind.cfg.MapperBuilder;
+import tools.jackson.databind.deser.jdk.JavaUtilCalendarDeserializer;
+import tools.jackson.databind.deser.jdk.JavaUtilDateDeserializer;
+import tools.jackson.databind.deser.jdk.NumberDeserializers.BigDecimalDeserializer;
+import tools.jackson.databind.deser.jdk.NumberDeserializers.BigIntegerDeserializer;
+import tools.jackson.databind.deser.std.StdDeserializer;
+import tools.jackson.databind.exc.MismatchedInputException;
+import tools.jackson.databind.json.JsonMapper;
+import tools.jackson.databind.json.JsonMapper.Builder;
+import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
+import tools.jackson.databind.jsontype.TypeDeserializer;
+import tools.jackson.databind.jsontype.TypeSerializer;
+import tools.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer;
+import tools.jackson.databind.module.SimpleDeserializers;
+import tools.jackson.databind.module.SimpleSerializers;
+import tools.jackson.databind.ser.Serializers;
+import tools.jackson.databind.ser.jdk.JavaUtilCalendarSerializer;
+import tools.jackson.databind.ser.jdk.JavaUtilDateSerializer;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import org.springframework.data.mapping.MappingException;
+import org.springframework.data.redis.support.collections.CollectionUtils;
+import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+
+/**
+ * {@link ObjectMapper} based {@link HashMapper} implementation that allows flattening. Given an entity {@code Person}
+ * with an {@code Address} like below the flattening will create individual hash entries for all nested properties and
+ * resolve complex types into simple types, as far as possible.
+ *
+ * Flattening requires all property names to not interfere with JSON paths. Using dots or brackets in map keys or as
+ * property names is not supported using flattening. The resulting hash cannot be mapped back into an Object.
+ *
Example
+ *
+ *
+ * class Person {
+ * String firstname;
+ * String lastname;
+ * Address address;
+ * Date date;
+ * LocalDateTime localDateTime;
+ * }
+ *
+ * class Address {
+ * String city;
+ * String country;
+ * }
+ *
+ *
+ * Normal
+ *
+ *
+ * Hash field
+ * Value
+ *
+ *
+ * firstname
+ * Jon
+ *
+ *
+ * lastname
+ * Snow
+ *
+ *
+ * address
+ * { "city" : "Castle Black", "country" : "The North" }
+ *
+ *
+ * date
+ * 1561543964015
+ *
+ *
+ * localDateTime
+ * 2018-01-02T12:13:14
+ *
+ *
+ * Flat
+ *
+ *
+ * Hash field
+ * Value
+ *
+ *
+ * firstname
+ * Jon
+ *
+ *
+ * lastname
+ * Snow
+ *
+ *
+ * address.city
+ * Castle Black
+ *
+ *
+ * address.country
+ * The North
+ *
+ *
+ * date
+ * 1561543964015
+ *
+ *
+ * localDateTime
+ * 2018-01-02T12:13:14
+ *
+ *
+ *
+ * @author Christoph Strobl
+ * @since 4.0
+ */
+public class Jackson3HashMapper implements HashMapper {
+
+ private static final boolean SOURCE_VERSION_PRESENT = ClassUtils.isPresent("javax.lang.model.SourceVersion",
+ Jackson3HashMapper.class.getClassLoader());
+
+ private final ObjectMapper typingMapper;
+ private final ObjectMapper untypedMapper;
+ private final boolean flatten;
+
+ public Jackson3HashMapper(
+ Consumer>> jsonMapperBuilder,
+ boolean flatten) {
+ this(((Supplier) () -> {
+ Builder builder = JsonMapper.builder();
+ jsonMapperBuilder.accept(builder);
+ return builder.build();
+ }).get(), flatten);
+ }
+
+ public static void preconfigure(MapperBuilder extends ObjectMapper, ? extends MapperBuilder, ?>> builder) {
+ builder.findAndAddModules().addModules(new HashMapperModule())
+ .activateDefaultTypingAsProperty(BasicPolymorphicTypeValidator.builder().allowIfBaseType(Object.class)
+ .allowIfSubType((ctx, clazz) -> true).build(), DefaultTyping.NON_FINAL, "@class")
+ .configure(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY, false)
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .changeDefaultPropertyInclusion(value -> value.withValueInclusion(Include.NON_NULL));
+ }
+
+ /**
+ * Creates new {@link Jackson3HashMapper} initialized with a custom Jackson {@link ObjectMapper}.
+ *
+ * @param mapper Jackson {@link ObjectMapper} used to de/serialize hashed {@link Object objects}; must not be
+ * {@literal null}.
+ * @param flatten boolean used to configure whether JSON de/serialized {@link Object} properties will be un/flattened
+ * using {@literal dot notation}, or whether to retain the hierarchical node structure created by Jackson.
+ */
+ public Jackson3HashMapper(ObjectMapper mapper, boolean flatten) {
+
+ Assert.notNull(mapper, "Mapper must not be null");
+
+ this.flatten = flatten;
+ this.typingMapper = mapper;
+ this.untypedMapper = JsonMapper.shared();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Map toHash(Object source) {
+
+ JsonNode tree = this.typingMapper.valueToTree(source);
+ return this.flatten ? flattenMap(tree.properties()) : this.untypedMapper.convertValue(tree, Map.class);
+ }
+
+ @Override
+ @SuppressWarnings("all")
+ public Object fromHash(Map hash) {
+
+ try {
+ if (this.flatten) {
+
+ Map unflattenedHash = doUnflatten(hash);
+ byte[] unflattenedHashedBytes = this.untypedMapper.writeValueAsBytes(unflattenedHash);
+ Object hashedObject = this.typingMapper.reader().forType(Object.class).readValue(unflattenedHashedBytes);
+
+ return hashedObject;
+ }
+
+ return this.typingMapper.treeToValue(this.untypedMapper.valueToTree(hash), Object.class);
+
+ } catch (Exception ex) {
+ throw new MappingException(ex.getMessage(), ex);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map doUnflatten(Map source) {
+
+ Map result = org.springframework.util.CollectionUtils.newLinkedHashMap(source.size());
+ Set treatSeparate = org.springframework.util.CollectionUtils.newLinkedHashSet(source.size());
+
+ for (Entry entry : source.entrySet()) {
+
+ String key = entry.getKey();
+ String[] keyParts = key.split("\\.");
+
+ if (keyParts.length == 1 && isNotIndexed(keyParts[0])) {
+ result.put(key, entry.getValue());
+ } else if (keyParts.length == 1 && isIndexed(keyParts[0])) {
+
+ String indexedKeyName = keyParts[0];
+ String nonIndexedKeyName = stripIndex(indexedKeyName);
+
+ int index = getIndex(indexedKeyName);
+
+ if (result.containsKey(nonIndexedKeyName)) {
+ addValueToTypedListAtIndex((List) result.get(nonIndexedKeyName), index, entry.getValue());
+ } else {
+ result.put(nonIndexedKeyName, createTypedListWithValue(index, entry.getValue()));
+ }
+ } else {
+ treatSeparate.add(keyParts[0]);
+ }
+ }
+
+ for (String partial : treatSeparate) {
+
+ Map newSource = new LinkedHashMap<>();
+
+ // Copies all nested, dot properties from the source Map to the new Map beginning from
+ // the next nested (dot) property
+ for (Entry entry : source.entrySet()) {
+ String key = entry.getKey();
+ if (key.startsWith(partial)) {
+ String keyAfterDot = key.substring(partial.length() + 1);
+ newSource.put(keyAfterDot, entry.getValue());
+ }
+ }
+
+ if (isNonNestedIndexed(partial)) {
+
+ String nonIndexPartial = stripIndex(partial);
+ int index = getIndex(partial);
+
+ if (result.containsKey(nonIndexPartial)) {
+ addValueToTypedListAtIndex((List) result.get(nonIndexPartial), index, doUnflatten(newSource));
+ } else {
+ result.put(nonIndexPartial, createTypedListWithValue(index, doUnflatten(newSource)));
+ }
+ } else {
+ result.put(partial, doUnflatten(newSource));
+ }
+ }
+
+ return result;
+ }
+
+ private boolean isIndexed(String value) {
+ return value.indexOf('[') > -1;
+ }
+
+ private boolean isNotIndexed(String value) {
+ return !isIndexed(value);
+ }
+
+ private boolean isNonNestedIndexed(String value) {
+ return value.endsWith("]");
+ }
+
+ private int getIndex(String indexedValue) {
+ return Integer.parseInt(indexedValue.substring(indexedValue.indexOf('[') + 1, indexedValue.length() - 1));
+ }
+
+ private String stripIndex(String indexedValue) {
+
+ int indexOfLeftBracket = indexedValue.indexOf("[");
+
+ return indexOfLeftBracket > -1 ? indexedValue.substring(0, indexOfLeftBracket) : indexedValue;
+ }
+
+ private Map flattenMap(Set> source) {
+
+ Map resultMap = new HashMap<>();
+ doFlatten("", source, resultMap);
+ return resultMap;
+ }
+
+ private void doFlatten(String propertyPrefix, Set> inputMap, Map resultMap) {
+
+ if (StringUtils.hasText(propertyPrefix)) {
+ propertyPrefix = propertyPrefix + ".";
+ }
+
+ for (Entry entry : inputMap) {
+ flattenElement(propertyPrefix + entry.getKey(), entry.getValue(), resultMap);
+ }
+ }
+
+ private void flattenElement(String propertyPrefix, Object source, Map resultMap) {
+
+ if (!(source instanceof JsonNode element)) {
+ resultMap.put(propertyPrefix, source);
+ return;
+ }
+
+ if (element.isArray()) {
+
+ Iterator nodes = element.values().iterator();
+
+ while (nodes.hasNext()) {
+
+ JsonNode currentNode = nodes.next();
+
+ if (currentNode.isArray()) {
+ flattenCollection(propertyPrefix, currentNode.values(), resultMap);
+ } else if (nodes.hasNext() && mightBeJavaType(currentNode)) {
+
+ JsonNode next = nodes.next();
+
+ if (next.isArray()) {
+ flattenCollection(propertyPrefix, next.values(), resultMap);
+ }
+ if (currentNode.asString().equals("java.util.Date")) {
+ resultMap.put(propertyPrefix, next.asString());
+ break;
+ }
+ if (next.isNumber()) {
+ resultMap.put(propertyPrefix, next.numberValue());
+ break;
+ }
+ if (next.isString()) {
+ resultMap.put(propertyPrefix, next.stringValue());
+ break;
+ }
+ if (next.isBoolean()) {
+ resultMap.put(propertyPrefix, next.booleanValue());
+ break;
+ }
+ if (next.isBinary()) {
+
+ try {
+ resultMap.put(propertyPrefix, next.binaryValue());
+ } catch (Exception ex) {
+ throw new IllegalStateException("Cannot read binary value '%s'".formatted(propertyPrefix), ex);
+ }
+
+ break;
+ }
+ }
+ }
+ } else if (element.isObject()) {
+ doFlatten(propertyPrefix, element.properties(), resultMap);
+ } else {
+
+ switch (element.getNodeType()) {
+ case STRING -> resultMap.put(propertyPrefix, element.stringValue());
+ case NUMBER -> resultMap.put(propertyPrefix, element.numberValue());
+ case BOOLEAN -> resultMap.put(propertyPrefix, element.booleanValue());
+ case BINARY -> {
+ try {
+ resultMap.put(propertyPrefix, element.binaryValue());
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ default ->
+ resultMap.put(propertyPrefix, new DirectFieldAccessFallbackBeanWrapper(element).getPropertyValue("_value"));
+ }
+ }
+ }
+
+ private boolean mightBeJavaType(JsonNode node) {
+
+ String textValue = node.asString();
+
+ if (!SOURCE_VERSION_PRESENT) {
+ return Arrays.asList("java.util.Date", "java.math.BigInteger", "java.math.BigDecimal").contains(textValue);
+ }
+
+ return javax.lang.model.SourceVersion.isName(textValue);
+ }
+
+ private void flattenCollection(String propertyPrefix, Collection list, Map resultMap) {
+
+ Iterator iterator = list.iterator();
+ for (int counter = 0; iterator.hasNext(); counter++) {
+ JsonNode element = iterator.next();
+ flattenElement(propertyPrefix + "[" + counter + "]", element, resultMap);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void addValueToTypedListAtIndex(List listWithTypeHint, int index, Object value) {
+
+ List valueList = (List) listWithTypeHint.get(1);
+
+ if (index >= valueList.size()) {
+ int initialCapacity = index + 1;
+ List newValueList = new ArrayList<>(initialCapacity);
+ Collections.copy(CollectionUtils.initializeList(newValueList, initialCapacity), valueList);
+ listWithTypeHint.set(1, newValueList);
+ valueList = newValueList;
+ }
+
+ valueList.set(index, value);
+ }
+
+ private List createTypedListWithValue(int index, Object value) {
+
+ int initialCapacity = index + 1;
+
+ List valueList = CollectionUtils.initializeList(new ArrayList<>(initialCapacity), initialCapacity);
+ valueList.set(index, value);
+
+ List listWithTypeHint = new ArrayList<>();
+ listWithTypeHint.add(ArrayList.class.getName());
+ listWithTypeHint.add(valueList);
+
+ return listWithTypeHint;
+ }
+
+ private static class HashMapperModule extends JacksonModule {
+
+ @Override
+ public String getModuleName() {
+ return "spring-data-hash-mapper-module";
+ }
+
+ @Override
+ public Version version() {
+ return new Version(4, 0, 0, null, "org.springframework.data", "spring-data-redis");
+ }
+
+ @Override
+ public void setupModule(SetupContext context) {
+
+ List> valueSerializers = new ArrayList<>();
+ valueSerializers.add(new JavaUtilDateSerializer(true, null) {
+ @Override
+ public void serializeWithType(Date value, JsonGenerator g, SerializationContext ctxt, TypeSerializer typeSer)
+ throws JacksonException {
+ serialize(value, g, ctxt);
+ }
+ });
+ valueSerializers.add(new UTCCalendarSerializer());
+
+ Serializers serializers = new SimpleSerializers(valueSerializers);
+ context.addSerializers(serializers);
+
+ Map, ValueDeserializer>> valueDeserializers = new LinkedHashMap<>();
+ valueDeserializers.put(java.util.Calendar.class,
+ new UntypedFallbackDeserializer<>(new UntypedUTCCalendarDeserializer()));
+ valueDeserializers.put(java.util.Date.class, new UntypedFallbackDeserializer<>(new JavaUtilDateDeserializer()));
+ valueDeserializers.put(BigInteger.class, new UntypedFallbackDeserializer<>(new BigIntegerDeserializer()));
+ valueDeserializers.put(BigDecimal.class, new UntypedFallbackDeserializer<>(new BigDecimalDeserializer()));
+
+ context.addDeserializers(new SimpleDeserializers(valueDeserializers));
+ }
+
+ }
+
+ static class UntypedFallbackDeserializer extends StdDeserializer {
+
+ private final StdDeserializer> delegate;
+
+ protected UntypedFallbackDeserializer(StdDeserializer> delegate) {
+ super(Object.class);
+ this.delegate = delegate;
+ }
+
+ @Override
+ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer)
+ throws JacksonException {
+
+ if (!(typeDeserializer instanceof AsPropertyTypeDeserializer asPropertySerializer)) {
+ return super.deserializeWithType(p, ctxt, typeDeserializer);
+ }
+
+ try {
+ return super.deserializeWithType(p, ctxt, typeDeserializer);
+ } catch (MismatchedInputException e) {
+ if (!asPropertySerializer.baseType().isTypeOrSuperTypeOf(delegate.handledType())) {
+ throw e;
+ }
+ }
+
+ return deserialize(p, ctxt);
+
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
+ return (T) delegate.deserialize(p, ctxt);
+ }
+ }
+
+ static class UTCCalendarSerializer extends JavaUtilCalendarSerializer {
+
+ private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
+
+ @Override
+ public void serialize(Calendar value, JsonGenerator g, SerializationContext provider) throws JacksonException {
+
+ Calendar utc = Calendar.getInstance();
+ utc.setTimeInMillis(value.getTimeInMillis());
+ utc.setTimeZone(UTC);
+ super.serialize(utc, g, provider);
+ }
+
+ @Override
+ public void serializeWithType(Calendar value, JsonGenerator g, SerializationContext ctxt, TypeSerializer typeSer)
+ throws JacksonException {
+ serialize(value, g, ctxt);
+ }
+ }
+
+ static class UntypedUTCCalendarDeserializer extends JavaUtilCalendarDeserializer {
+
+ private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
+
+ @Override
+ public Calendar deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
+
+ Calendar cal = super.deserialize(p, ctxt);
+
+ Calendar utc = Calendar.getInstance(UTC);
+ utc.setTimeInMillis(cal.getTimeInMillis());
+ utc.setTimeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));
+
+ return utc;
+ }
+ }
+}
diff --git a/src/main/java/org/springframework/data/redis/serializer/Jackson2JsonRedisSerializer.java b/src/main/java/org/springframework/data/redis/serializer/Jackson2JsonRedisSerializer.java
index 32d626bdd8..7b6ba4d9ba 100644
--- a/src/main/java/org/springframework/data/redis/serializer/Jackson2JsonRedisSerializer.java
+++ b/src/main/java/org/springframework/data/redis/serializer/Jackson2JsonRedisSerializer.java
@@ -40,7 +40,9 @@
* @author Thomas Darimont
* @author Mark Paluch
* @since 1.2
+ * @deprecated since 4.0 in favor of {@link Jackson3JsonRedisSerializer}.
*/
+@Deprecated(since = "4.0", forRemoval = true)
public class Jackson2JsonRedisSerializer implements RedisSerializer {
/**
diff --git a/src/main/java/org/springframework/data/redis/serializer/Jackson3JsonRedisSerializer.java b/src/main/java/org/springframework/data/redis/serializer/Jackson3JsonRedisSerializer.java
new file mode 100644
index 0000000000..15cd981176
--- /dev/null
+++ b/src/main/java/org/springframework/data/redis/serializer/Jackson3JsonRedisSerializer.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2011-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.serializer;
+
+import tools.jackson.databind.JavaType;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.ser.SerializerFactory;
+import tools.jackson.databind.type.TypeFactory;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+/**
+ * {@link RedisSerializer} that can read and write JSON using
+ * Jackson's and
+ * Jackson Databind {@link ObjectMapper}.
+ *
+ * This serializer can be used to bind to typed beans, or untyped {@link java.util.HashMap HashMap} instances.
+ * Note: Null objects are serialized as empty arrays and vice versa.
+ *
+ * JSON reading and writing can be customized by configuring {@link JacksonObjectReader} respective
+ * {@link JacksonObjectWriter}.
+ *
+ * @author Thomas Darimont
+ * @author Mark Paluch
+ * @since 1.2
+ */
+public class Jackson3JsonRedisSerializer implements RedisSerializer {
+
+ /**
+ * @deprecated since 3.0 for removal.
+ */
+ @Deprecated(since = "3.0", forRemoval = true) //
+ public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
+
+ private final JavaType javaType;
+
+ private ObjectMapper mapper;
+
+ private final Jackson3ObjectReader reader;
+
+ private final Jackson3ObjectWriter writer;
+
+ /**
+ * Creates a new {@link Jackson3JsonRedisSerializer} for the given target {@link Class}.
+ *
+ * @param type must not be {@literal null}.
+ */
+ public Jackson3JsonRedisSerializer(Class type) {
+ this(new ObjectMapper(), type);
+ }
+
+ /**
+ * Creates a new {@link Jackson3JsonRedisSerializer} for the given target {@link JavaType}.
+ *
+ * @param javaType must not be {@literal null}.
+ */
+ public Jackson3JsonRedisSerializer(JavaType javaType) {
+ this(new ObjectMapper(), javaType);
+ }
+
+ /**
+ * Creates a new {@link Jackson3JsonRedisSerializer} for the given target {@link Class}.
+ *
+ * @param mapper must not be {@literal null}.
+ * @param type must not be {@literal null}.
+ * @since 3.0
+ */
+ public Jackson3JsonRedisSerializer(ObjectMapper mapper, Class type) {
+
+ Assert.notNull(mapper, "ObjectMapper must not be null");
+ Assert.notNull(type, "Java type must not be null");
+
+ this.javaType = getJavaType(type);
+ this.mapper = mapper;
+ this.reader = Jackson3ObjectReader.create();
+ this.writer = Jackson3ObjectWriter.create();
+ }
+
+ /**
+ * Creates a new {@link Jackson3JsonRedisSerializer} for the given target {@link JavaType}.
+ *
+ * @param mapper must not be {@literal null}.
+ * @param javaType must not be {@literal null}.
+ * @since 3.0
+ */
+ public Jackson3JsonRedisSerializer(ObjectMapper mapper, JavaType javaType) {
+ this(mapper, javaType, Jackson3ObjectReader.create(), Jackson3ObjectWriter.create());
+ }
+
+ /**
+ * Creates a new {@link Jackson3JsonRedisSerializer} for the given target {@link JavaType}.
+ *
+ * @param mapper must not be {@literal null}.
+ * @param javaType must not be {@literal null}.
+ * @param reader the {@link JacksonObjectReader} function to read objects using {@link ObjectMapper}.
+ * @param writer the {@link JacksonObjectWriter} function to write objects using {@link ObjectMapper}.
+ * @since 3.0
+ */
+ public Jackson3JsonRedisSerializer(ObjectMapper mapper, JavaType javaType, Jackson3ObjectReader reader,
+ Jackson3ObjectWriter writer) {
+
+ Assert.notNull(mapper, "ObjectMapper must not be null!");
+ Assert.notNull(reader, "Reader must not be null!");
+ Assert.notNull(writer, "Writer must not be null!");
+
+ this.mapper = mapper;
+ this.reader = reader;
+ this.writer = writer;
+ this.javaType = javaType;
+ }
+
+ /**
+ * Sets the {@code ObjectMapper} for this view. If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper}
+ * is used.
+ *
+ * Setting a custom-configured {@code ObjectMapper} is one way to take further control of the JSON serialization
+ * process. For example, an extended {@link SerializerFactory} can be configured that provides custom serializers for
+ * specific types. The other option for refining the serialization process is to use Jackson's provided annotations on
+ * the types to be serialized, in which case a custom-configured ObjectMapper is unnecessary.
+ *
+ * @deprecated since 3.0, use {@link #Jackson3JsonRedisSerializer(ObjectMapper, Class) constructor creation} to
+ * configure the object mapper.
+ */
+ @Deprecated(since = "3.0", forRemoval = true)
+ public void setObjectMapper(ObjectMapper mapper) {
+
+ Assert.notNull(mapper, "'objectMapper' must not be null");
+ this.mapper = mapper;
+ }
+
+ @Override
+ public byte[] serialize(@Nullable T value) throws SerializationException {
+
+ if (value == null) {
+ return SerializationUtils.EMPTY_ARRAY;
+ }
+ try {
+ return this.writer.write(this.mapper, value);
+ } catch (Exception ex) {
+ throw new SerializationException("Could not write JSON: " + ex.getMessage(), ex);
+ }
+ }
+
+ @Nullable
+ @Override
+ @SuppressWarnings("unchecked")
+ public T deserialize(@Nullable byte[] bytes) throws SerializationException {
+
+ if (SerializationUtils.isEmpty(bytes)) {
+ return null;
+ }
+ try {
+ return (T) this.reader.read(this.mapper, bytes, javaType);
+ } catch (Exception ex) {
+ throw new SerializationException("Could not read JSON: " + ex.getMessage(), ex);
+ }
+ }
+
+ /**
+ * Returns the Jackson {@link JavaType} for the specific class.
+ *
+ * Default implementation returns {@link TypeFactory#constructType(java.lang.reflect.Type)}, but this can be
+ * overridden in subclasses, to allow for custom generic collection handling. For instance:
+ *
+ *
+ * protected JavaType getJavaType(Class<?> clazz) {
+ * if (List.class.isAssignableFrom(clazz)) {
+ * return TypeFactory.defaultInstance().constructCollectionType(ArrayList.class, MyBean.class);
+ * } else {
+ * return super.getJavaType(clazz);
+ * }
+ * }
+ *
+ *
+ * @param clazz the class to return the java type for
+ * @return the java type
+ */
+ protected JavaType getJavaType(Class> clazz) {
+ return TypeFactory.unsafeSimpleType(clazz);
+ }
+}
diff --git a/src/main/java/org/springframework/data/redis/serializer/Jackson3ObjectReader.java b/src/main/java/org/springframework/data/redis/serializer/Jackson3ObjectReader.java
new file mode 100644
index 0000000000..ee8b880194
--- /dev/null
+++ b/src/main/java/org/springframework/data/redis/serializer/Jackson3ObjectReader.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2022-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.serializer;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import tools.jackson.databind.JavaType;
+import tools.jackson.databind.ObjectMapper;
+
+/**
+ * Defines the contract for Object Mapping readers. Implementations of this interface can deserialize a given byte array
+ * holding JSON to an Object considering the target type.
+ *
+ * Reader functions can customize how the actual JSON is being deserialized by e.g. obtaining a customized
+ * {@link com.fasterxml.jackson.databind.ObjectReader} applying serialization features, date formats, or views.
+ *
+ * @author Mark Paluch
+ * @since 3.0
+ */
+@FunctionalInterface
+public interface Jackson3ObjectReader {
+
+ /**
+ * Read an object graph from the given root JSON into a Java object considering the {@link JavaType}.
+ *
+ * @param mapper the object mapper to use.
+ * @param source the JSON to deserialize.
+ * @param type the Java target type
+ * @return the deserialized Java object.
+ * @throws IOException if an I/O error or JSON deserialization error occurs.
+ */
+ Object read(ObjectMapper mapper, byte[] source, JavaType type) throws IOException;
+
+ /**
+ * Create a default {@link Jackson3ObjectReader} delegating to {@link ObjectMapper#readValue(InputStream, JavaType)}.
+ *
+ * @return the default {@link Jackson3ObjectReader}.
+ */
+ static Jackson3ObjectReader create() {
+ return (mapper, source, type) -> mapper.readValue(source, 0, source.length, type);
+ }
+
+}
diff --git a/src/main/java/org/springframework/data/redis/serializer/Jackson3ObjectWriter.java b/src/main/java/org/springframework/data/redis/serializer/Jackson3ObjectWriter.java
new file mode 100644
index 0000000000..f995bcc123
--- /dev/null
+++ b/src/main/java/org/springframework/data/redis/serializer/Jackson3ObjectWriter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2022-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.serializer;
+
+import java.io.IOException;
+
+import tools.jackson.databind.ObjectMapper;
+
+/**
+ * Defines the contract for Object Mapping writers. Implementations of this interface can serialize a given Object to a
+ * {@code byte[]} containing JSON.
+ *
+ * Writer functions can customize how the actual JSON is being written by e.g. obtaining a customized
+ * {@link com.fasterxml.jackson.databind.ObjectWriter} applying serialization features, date formats, or views.
+ *
+ * @author Mark Paluch
+ * @since 3.0
+ */
+@FunctionalInterface
+public interface Jackson3ObjectWriter {
+
+ /**
+ * Write the object graph with the given root {@code source} as byte array.
+ *
+ * @param mapper the object mapper to use.
+ * @param source the root of the object graph to marshal.
+ * @return a byte array containing the serialized object graph.
+ * @throws IOException if an I/O error or JSON serialization error occurs.
+ */
+ byte[] write(ObjectMapper mapper, Object source) throws IOException;
+
+ /**
+ * Create a default {@link Jackson3ObjectWriter} delegating to {@link ObjectMapper#writeValueAsBytes(Object)}.
+ *
+ * @return the default {@link Jackson3ObjectWriter}.
+ */
+ static Jackson3ObjectWriter create() {
+ return ObjectMapper::writeValueAsBytes;
+ }
+
+}
diff --git a/src/main/java/org/springframework/data/redis/serializer/JacksonObjectReader.java b/src/main/java/org/springframework/data/redis/serializer/JacksonObjectReader.java
index e2c1d943ec..9da953f8dd 100644
--- a/src/main/java/org/springframework/data/redis/serializer/JacksonObjectReader.java
+++ b/src/main/java/org/springframework/data/redis/serializer/JacksonObjectReader.java
@@ -30,8 +30,10 @@
*
* @author Mark Paluch
* @since 3.0
+ * @deprecated since 4.0 in favor of {@link Jackson3ObjectReader}.
*/
@FunctionalInterface
+@Deprecated(since = "4.0", forRemoval = true)
public interface JacksonObjectReader {
/**
diff --git a/src/main/java/org/springframework/data/redis/serializer/JacksonObjectWriter.java b/src/main/java/org/springframework/data/redis/serializer/JacksonObjectWriter.java
index 88db313130..5d13b67b66 100644
--- a/src/main/java/org/springframework/data/redis/serializer/JacksonObjectWriter.java
+++ b/src/main/java/org/springframework/data/redis/serializer/JacksonObjectWriter.java
@@ -28,8 +28,10 @@
*
* @author Mark Paluch
* @since 3.0
+ * @deprecated since 4.0 in favor of {@link Jackson3ObjectWriter}.
*/
@FunctionalInterface
+@Deprecated(since = "4.0", forRemoval = true)
public interface JacksonObjectWriter {
/**
diff --git a/src/test/java/org/springframework/data/redis/core/AbstractOperationsTestParams.java b/src/test/java/org/springframework/data/redis/core/AbstractOperationsTestParams.java
index 485b6b1414..91fb53ec80 100644
--- a/src/test/java/org/springframework/data/redis/core/AbstractOperationsTestParams.java
+++ b/src/test/java/org/springframework/data/redis/core/AbstractOperationsTestParams.java
@@ -34,6 +34,7 @@
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.Jackson3JsonRedisSerializer;
import org.springframework.data.redis.serializer.OxmSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.data.redis.test.XstreamOxmSerializerSingleton;
@@ -109,6 +110,12 @@ public static Collection testParams(RedisConnectionFactory connectionF
jackson2JsonPersonTemplate.setValueSerializer(jackson2JsonSerializer);
jackson2JsonPersonTemplate.afterPropertiesSet();
+ Jackson3JsonRedisSerializer jackson3JsonSerializer = new Jackson3JsonRedisSerializer<>(Person.class);
+ RedisTemplate jackson3JsonPersonTemplate = new RedisTemplate<>();
+ jackson3JsonPersonTemplate.setConnectionFactory(connectionFactory);
+ jackson3JsonPersonTemplate.setValueSerializer(jackson3JsonSerializer);
+ jackson3JsonPersonTemplate.afterPropertiesSet();
+
GenericJackson2JsonRedisSerializer genericJackson2JsonSerializer = new GenericJackson2JsonRedisSerializer();
RedisTemplate genericJackson2JsonPersonTemplate = new RedisTemplate<>();
genericJackson2JsonPersonTemplate.setConnectionFactory(connectionFactory);
@@ -124,6 +131,7 @@ public static Collection testParams(RedisConnectionFactory connectionF
{ xstreamStringTemplate, stringFactory, stringFactory }, //
{ xstreamPersonTemplate, stringFactory, personFactory }, //
{ jackson2JsonPersonTemplate, stringFactory, personFactory }, //
+ { jackson3JsonPersonTemplate, stringFactory, personFactory }, //
{ genericJackson2JsonPersonTemplate, stringFactory, personFactory } });
}
}
diff --git a/src/test/java/org/springframework/data/redis/core/ReactiveOperationsTestParams.java b/src/test/java/org/springframework/data/redis/core/ReactiveOperationsTestParams.java
index 77895e3bb2..250d7975e8 100644
--- a/src/test/java/org/springframework/data/redis/core/ReactiveOperationsTestParams.java
+++ b/src/test/java/org/springframework/data/redis/core/ReactiveOperationsTestParams.java
@@ -34,6 +34,7 @@
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.Jackson3JsonRedisSerializer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.OxmSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
@@ -100,6 +101,10 @@ RedisSerializationContext. newSerializationContext(jdkSerializat
ReactiveRedisTemplate jackson2JsonPersonTemplate = new ReactiveRedisTemplate(
lettuceConnectionFactory, RedisSerializationContext.fromSerializer(jackson2JsonSerializer));
+ Jackson3JsonRedisSerializer jackson3JsonSerializer = new Jackson3JsonRedisSerializer<>(Person.class);
+ ReactiveRedisTemplate jackson3JsonPersonTemplate = new ReactiveRedisTemplate(
+ lettuceConnectionFactory, RedisSerializationContext.fromSerializer(jackson3JsonSerializer));
+
GenericJackson2JsonRedisSerializer genericJackson2JsonSerializer = new GenericJackson2JsonRedisSerializer();
ReactiveRedisTemplate genericJackson2JsonPersonTemplate = new ReactiveRedisTemplate(
lettuceConnectionFactory, RedisSerializationContext.fromSerializer(genericJackson2JsonSerializer));
@@ -115,6 +120,7 @@ RedisSerializationContext. newSerializationContext(jdkSerializat
new Fixture<>(xstreamStringTemplate, stringFactory, stringFactory, oxmSerializer, "String/OXM"), //
new Fixture<>(xstreamPersonTemplate, stringFactory, personFactory, oxmSerializer, "String/Person/OXM"), //
new Fixture<>(jackson2JsonPersonTemplate, stringFactory, personFactory, jackson2JsonSerializer, "Jackson2"), //
+ new Fixture<>(jackson3JsonPersonTemplate, stringFactory, personFactory, jackson2JsonSerializer, "Jackson3"), //
new Fixture<>(genericJackson2JsonPersonTemplate, stringFactory, personFactory, genericJackson2JsonSerializer,
"Generic Jackson 2"));
diff --git a/src/test/java/org/springframework/data/redis/mapping/AbstractHashMapperTests.java b/src/test/java/org/springframework/data/redis/mapping/AbstractHashMapperTests.java
index 3807d13680..1d3fa8e256 100644
--- a/src/test/java/org/springframework/data/redis/mapping/AbstractHashMapperTests.java
+++ b/src/test/java/org/springframework/data/redis/mapping/AbstractHashMapperTests.java
@@ -39,6 +39,7 @@ protected void assertBackAndForwardMapping(Object o) {
HashMapper mapper = mapperFor(o.getClass());
Map hash = mapper.toHash(o);
+ System.out.println("hash: " + hash);
assertThat(mapper.fromHash(hash)).isEqualTo(o);
}
diff --git a/src/test/java/org/springframework/data/redis/mapping/Jackson3CompatibilityTests.java b/src/test/java/org/springframework/data/redis/mapping/Jackson3CompatibilityTests.java
new file mode 100644
index 0000000000..e67e802081
--- /dev/null
+++ b/src/test/java/org/springframework/data/redis/mapping/Jackson3CompatibilityTests.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.redis.mapping;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Map;
+
+import org.junit.jupiter.api.Disabled;
+import org.springframework.data.redis.hash.Jackson2HashMapper;
+import org.springframework.data.redis.hash.Jackson3HashMapper;
+
+/**
+ * @author Christoph Strobl
+ */
+public class Jackson3CompatibilityTests extends Jackson3HashMapperUnitTests {
+
+ private final Jackson2HashMapper jackson2HashMapper;
+
+ public Jackson3CompatibilityTests() {
+ super(new Jackson3HashMapper(Jackson3HashMapper::preconfigure, false));
+ this.jackson2HashMapper = new Jackson2HashMapper(false);
+ }
+
+ @Override
+ @Disabled("with jackson 2 this used to render the timestamp as string. Now its a long and in line with calendar timestamp")
+ void dateValueShouldBeTreatedCorrectly() {
+ super.dateValueShouldBeTreatedCorrectly();
+ }
+
+ @Override
+ @Disabled("with jackson 2 used to render the enum and its type hint in an array. Now its just the enum value")
+ void enumsShouldBeTreatedCorrectly() {
+ super.enumsShouldBeTreatedCorrectly();
+ }
+
+ @Override
+ protected void assertBackAndForwardMapping(Object o) {
+
+ Map hash3 = getMapper().toHash(o);
+ Map hash2 = jackson2HashMapper.toHash(o);
+
+ assertThat(hash3).containsAllEntriesOf(hash2);
+ assertThat(getMapper().fromHash(hash2)).isEqualTo(o);
+ }
+}
diff --git a/src/test/java/org/springframework/data/redis/mapping/Jackson3FlatteningCompatibilityTests.java b/src/test/java/org/springframework/data/redis/mapping/Jackson3FlatteningCompatibilityTests.java
new file mode 100644
index 0000000000..2c9d337abe
--- /dev/null
+++ b/src/test/java/org/springframework/data/redis/mapping/Jackson3FlatteningCompatibilityTests.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.redis.mapping;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Map;
+
+import org.junit.jupiter.api.Disabled;
+import org.springframework.data.redis.hash.Jackson2HashMapper;
+import org.springframework.data.redis.hash.Jackson3HashMapper;
+
+/**
+ * @author Christoph Strobl
+ */
+public class Jackson3FlatteningCompatibilityTests extends Jackson3HashMapperUnitTests {
+
+ private final Jackson2HashMapper jackson2HashMapper;
+
+ public Jackson3FlatteningCompatibilityTests() {
+ super(new Jackson3HashMapper(Jackson3HashMapper::preconfigure, true));
+ this.jackson2HashMapper = new Jackson2HashMapper(true);
+ }
+
+ @Override
+ @Disabled("with jackson 2 this used to render the timestamp as string. Now its a long and in line with calendar timestamp")
+ void dateValueShouldBeTreatedCorrectly() {
+ super.dateValueShouldBeTreatedCorrectly();
+ }
+
+ @Override
+ @Disabled("with jackson 2 used to render the enum and its type hint in an array. Now its just the enum value")
+ void enumsShouldBeTreatedCorrectly() {
+ super.enumsShouldBeTreatedCorrectly();
+ }
+
+ @Override
+ protected void assertBackAndForwardMapping(Object o) {
+
+ Map hash3 = getMapper().toHash(o);
+ Map hash2 = jackson2HashMapper.toHash(o);
+
+ assertThat(hash3).containsAllEntriesOf(hash2);
+ assertThat(getMapper().fromHash(hash2)).isEqualTo(o);
+ }
+}
diff --git a/src/test/java/org/springframework/data/redis/mapping/Jackson3HashMapperFlatteningUnitTests.java b/src/test/java/org/springframework/data/redis/mapping/Jackson3HashMapperFlatteningUnitTests.java
new file mode 100644
index 0000000000..5e3e4b8835
--- /dev/null
+++ b/src/test/java/org/springframework/data/redis/mapping/Jackson3HashMapperFlatteningUnitTests.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2023-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.redis.mapping;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.data.redis.hash.Jackson3HashMapper;
+
+/**
+ * @author Christoph Strobl
+ * @author John Blum
+ * @since 2023/06
+ */
+public class Jackson3HashMapperFlatteningUnitTests extends Jackson3HashMapperUnitTests {
+
+ Jackson3HashMapperFlatteningUnitTests() {
+ super(new Jackson3HashMapper(Jackson3HashMapper::preconfigure,true));
+ }
+
+ @Test // GH-2593
+ void timestampHandledCorrectly() {
+
+ Map hash = Map.of("@class", Session.class.getName(), "lastAccessed", "2023-06-05T18:36:30");
+
+ // Map hash = Map.of("lastAccessed", "2023-06-05T18:36:30");
+
+ Session session = (Session) getMapper().fromHash(hash);
+
+ assertThat(session).isNotNull();
+ assertThat(session.lastAccessed).isEqualTo(LocalDateTime.of(2023, Month.JUNE, 5, 18, 36, 30));
+ }
+
+ private static class Session {
+
+ private LocalDateTime lastAccessed;
+
+ public LocalDateTime getLastAccessed() {
+ return lastAccessed;
+ }
+
+ public void setLastAccessed(LocalDateTime lastAccessed) {
+ this.lastAccessed = lastAccessed;
+ }
+ }
+}
diff --git a/src/test/java/org/springframework/data/redis/mapping/Jackson3HashMapperNonFlatteningUnitTests.java b/src/test/java/org/springframework/data/redis/mapping/Jackson3HashMapperNonFlatteningUnitTests.java
new file mode 100644
index 0000000000..af19ff6a26
--- /dev/null
+++ b/src/test/java/org/springframework/data/redis/mapping/Jackson3HashMapperNonFlatteningUnitTests.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2023-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.redis.mapping;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.data.redis.hash.Jackson3HashMapper;
+
+/**
+ * @author Christoph Strobl
+ * @author John Blum
+ * @since 2023/06
+ */
+public class Jackson3HashMapperNonFlatteningUnitTests extends Jackson3HashMapperUnitTests {
+
+ Jackson3HashMapperNonFlatteningUnitTests() {
+ super(new Jackson3HashMapper(Jackson3HashMapper::preconfigure, false));
+ }
+
+ @Test // GH-2593
+ void timestampHandledCorrectly() {
+
+ Session source = new Session();
+ source.lastAccessed = LocalDateTime.of(2023, Month.JUNE, 5, 18, 36, 30);
+
+ Map hash = getMapper().toHash(source);
+ Session session = (Session) getMapper().fromHash(hash);
+
+ assertThat(session).isNotNull();
+ assertThat(session.lastAccessed).isEqualTo(LocalDateTime.of(2023, Month.JUNE, 5, 18, 36, 30));
+ }
+
+ private static class Session {
+
+ private LocalDateTime lastAccessed;
+
+ public LocalDateTime getLastAccessed() {
+ return lastAccessed;
+ }
+
+ public void setLastAccessed(LocalDateTime lastAccessed) {
+ this.lastAccessed = lastAccessed;
+ }
+ }
+}
diff --git a/src/test/java/org/springframework/data/redis/mapping/Jackson3HashMapperUnitTests.java b/src/test/java/org/springframework/data/redis/mapping/Jackson3HashMapperUnitTests.java
new file mode 100644
index 0000000000..72631fdb64
--- /dev/null
+++ b/src/test/java/org/springframework/data/redis/mapping/Jackson3HashMapperUnitTests.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright 2016-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.mapping;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.data.redis.Address;
+import org.springframework.data.redis.Person;
+import org.springframework.data.redis.hash.HashMapper;
+import org.springframework.data.redis.hash.Jackson2HashMapper;
+import org.springframework.data.redis.hash.Jackson3HashMapper;
+
+/**
+ * Unit tests for {@link Jackson2HashMapper}.
+ *
+ * @author Christoph Strobl
+ * @author Mark Paluch
+ * @author John Blum
+ */
+public abstract class Jackson3HashMapperUnitTests extends AbstractHashMapperTests {
+
+ private final Jackson3HashMapper mapper;
+
+ public Jackson3HashMapperUnitTests(Jackson3HashMapper mapper) {
+
+ this.mapper = mapper;
+ }
+
+ protected Jackson3HashMapper getMapper() {
+ return this.mapper;
+ }
+
+ @Override
+ @SuppressWarnings("rawtypes")
+ protected HashMapper mapperFor(Class t) {
+ return getMapper();
+ }
+
+ @Test // DATAREDIS-423
+ void shouldMapTypedListOfSimpleType() {
+
+ WithList source = new WithList();
+ source.strings = Arrays.asList("spring", "data", "redis");
+ assertBackAndForwardMapping(source);
+ }
+
+ @Test // DATAREDIS-423
+ void shouldMapTypedListOfComplexType() {
+
+ WithList source = new WithList();
+
+ source.persons = Arrays.asList(new Person("jon", "snow", 19), new Person("tyrion", "lannister", 27));
+ assertBackAndForwardMapping(source);
+ }
+
+ @Test // DATAREDIS-423
+ void shouldMapTypedListOfComplexObjectWihtNestedElements() {
+
+ WithList source = new WithList();
+
+ Person jon = new Person("jon", "snow", 19);
+ Address adr = new Address();
+ adr.setStreet("the wall");
+ adr.setNumber(100);
+ jon.setAddress(adr);
+
+ source.persons = Arrays.asList(jon, new Person("tyrion", "lannister", 27));
+ assertBackAndForwardMapping(source);
+ }
+
+ @Test // DATAREDIS-423
+ void shouldMapNestedObject() {
+
+ Person jon = new Person("jon", "snow", 19);
+ Address adr = new Address();
+ adr.setStreet("the wall");
+ adr.setNumber(100);
+ jon.setAddress(adr);
+
+ assertBackAndForwardMapping(jon);
+ }
+
+ @Test // DATAREDIS-423
+ void shouldMapUntypedList() {
+
+ WithList source = new WithList();
+ source.objects = Arrays.asList(100, "foo", new Person("jon", "snow", 19));
+ assertBackAndForwardMapping(source);
+ }
+
+ @Test // DATAREDIS-423
+ void shouldMapTypedMapOfSimpleTypes() {
+
+ WithMap source = new WithMap();
+ source.strings = new LinkedHashMap<>();
+ source.strings.put("1", "spring");
+ source.strings.put("2", "data");
+ source.strings.put("3", "redis");
+ assertBackAndForwardMapping(source);
+ }
+
+ @Test // DATAREDIS-423
+ void shouldMapTypedMapOfComplexTypes() {
+
+ WithMap source = new WithMap();
+ source.persons = new LinkedHashMap<>();
+ source.persons.put("1", new Person("jon", "snow", 19));
+ source.persons.put("2", new Person("tyrion", "lannister", 19));
+ assertBackAndForwardMapping(source);
+ }
+
+ @Test // DATAREDIS-423
+ void shouldMapUntypedMap() {
+
+ WithMap source = new WithMap();
+ source.objects = new LinkedHashMap<>();
+ source.objects.put("1", "spring");
+ source.objects.put("2", 100);
+ source.objects.put("3", "redis");
+ assertBackAndForwardMapping(source);
+ }
+
+ @Test // DATAREDIS-423
+ void nestedStuff() {
+
+ WithList nestedList = new WithList();
+ nestedList.objects = new ArrayList<>();
+
+ WithMap deepNestedMap = new WithMap();
+ deepNestedMap.persons = new LinkedHashMap<>();
+ deepNestedMap.persons.put("jon", new Person("jon", "snow", 24));
+
+ nestedList.objects.add(deepNestedMap);
+
+ WithMap outer = new WithMap();
+ outer.objects = new LinkedHashMap<>();
+ outer.objects.put("1", nestedList);
+
+ assertBackAndForwardMapping(outer);
+ }
+
+ @Test // DATAREDIS-1001
+ void dateValueShouldBeTreatedCorrectly() {
+
+ WithDates source = new WithDates();
+ source.string = "id-1";
+ source.date = new Date(1561543964015L);
+ source.calendar = Calendar.getInstance();
+ source.localDate = LocalDate.parse("2018-01-02");
+ source.localDateTime = LocalDateTime.parse("2018-01-02T12:13:14");
+
+ assertBackAndForwardMapping(source);
+ }
+
+ @Test // GH-1566
+ @Disabled("Jackson removed default typing for final types")
+ void mapFinalClass() {
+
+ MeFinal source = new MeFinal();
+ source.value = "id-1";
+
+ assertBackAndForwardMapping(source);
+ }
+
+ @Test // GH-2365
+ void bigIntegerShouldBeTreatedCorrectly() {
+
+ WithBigWhatever source = new WithBigWhatever();
+ source.bigI = BigInteger.TEN;
+
+ assertBackAndForwardMapping(source);
+ }
+
+ @Test // GH-2365
+ void bigDecimalShouldBeTreatedCorrectly() {
+
+ WithBigWhatever source = new WithBigWhatever();
+ source.bigD = BigDecimal.ONE;
+
+ assertBackAndForwardMapping(source);
+ }
+
+ @Test // GH-2979
+ void enumsShouldBeTreatedCorrectly() {
+
+ WithEnumValue source = new WithEnumValue();
+ source.value = SpringDataEnum.REDIS;
+
+ assertBackAndForwardMapping(source);
+ }
+
+ public static class WithList {
+
+ List strings;
+ List objects;
+ List persons;
+
+ public List getStrings() {
+ return this.strings;
+ }
+
+ public void setStrings(List strings) {
+ this.strings = strings;
+ }
+
+ public List getObjects() {
+ return this.objects;
+ }
+
+ public void setObjects(List objects) {
+ this.objects = objects;
+ }
+
+ public List getPersons() {
+ return this.persons;
+ }
+
+ public void setPersons(List persons) {
+ this.persons = persons;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof WithList that)) {
+ return false;
+ }
+
+ return Objects.equals(this.getObjects(), that.getObjects())
+ && Objects.equals(this.getPersons(), that.getPersons())
+ && Objects.equals(this.getStrings(), that.getStrings());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getObjects(), getPersons(), getStrings());
+ }
+ }
+
+ public static class WithMap {
+
+ Map strings;
+ Map objects;
+ Map persons;
+
+ public Map getStrings() {
+ return this.strings;
+ }
+
+ public void setStrings(Map strings) {
+ this.strings = strings;
+ }
+
+ public Map getObjects() {
+ return this.objects;
+ }
+
+ public void setObjects(Map objects) {
+ this.objects = objects;
+ }
+
+ public Map getPersons() {
+ return this.persons;
+ }
+
+ public void setPersons(Map persons) {
+ this.persons = persons;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof WithMap that)) {
+ return false;
+ }
+
+ return Objects.equals(this.getObjects(), that.getObjects())
+ && Objects.equals(this.getPersons(), that.getPersons())
+ && Objects.equals(this.getStrings(), that.getStrings());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getObjects(), getPersons(), getStrings());
+ }
+ }
+
+ private static class WithDates {
+
+ private String string;
+ private Date date;
+ private Calendar calendar;
+ private LocalDate localDate;
+ private LocalDateTime localDateTime;
+
+ public String getString() {
+ return this.string;
+ }
+
+ public void setString(String string) {
+ this.string = string;
+ }
+
+ public Date getDate() {
+ return this.date;
+ }
+
+ public void setDate(Date date) {
+ this.date = date;
+ }
+
+ public Calendar getCalendar() {
+ return this.calendar;
+ }
+
+ public void setCalendar(Calendar calendar) {
+ this.calendar = calendar;
+ }
+
+ public LocalDate getLocalDate() {
+ return this.localDate;
+ }
+
+ public void setLocalDate(LocalDate localDate) {
+ this.localDate = localDate;
+ }
+
+ public LocalDateTime getLocalDateTime() {
+ return this.localDateTime;
+ }
+
+ public void setLocalDateTime(LocalDateTime localDateTime) {
+ this.localDateTime = localDateTime;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof WithDates that)) {
+ return false;
+ }
+
+ return Objects.equals(this.getString(), that.getString())
+ && Objects.equals(this.getCalendar(), that.getCalendar()) && Objects.equals(this.getDate(), that.getDate())
+ && Objects.equals(this.getLocalDate(), that.getLocalDate())
+ && Objects.equals(this.getLocalDateTime(), that.getLocalDateTime());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getString(), getCalendar(), getDate(), getLocalDate(), getLocalDateTime());
+ }
+
+ @Override
+ public String toString() {
+ return "WithDates{" + "string='" + string + '\'' + ", date=" + date + ", calendar=" + calendar + ", localDate="
+ + localDate + ", localDateTime=" + localDateTime + '}';
+ }
+ }
+
+ private static class WithBigWhatever {
+
+ private BigDecimal bigD;
+ private BigInteger bigI;
+
+ public BigDecimal getBigD() {
+ return this.bigD;
+ }
+
+ public void setBigD(BigDecimal bigD) {
+ this.bigD = bigD;
+ }
+
+ public BigInteger getBigI() {
+ return this.bigI;
+ }
+
+ public void setBigI(BigInteger bigI) {
+ this.bigI = bigI;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof WithBigWhatever that)) {
+ return false;
+ }
+
+ return Objects.equals(this.getBigD(), that.getBigD()) && Objects.equals(this.getBigI(), that.getBigI());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getBigD(), getBigI());
+ }
+ }
+
+ public static final class MeFinal {
+
+ private String value;
+
+ public String getValue() {
+ return this.value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof MeFinal that)) {
+ return false;
+ }
+
+ return Objects.equals(this.getValue(), that.getValue());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getValue());
+ }
+ }
+
+ enum SpringDataEnum {
+ COMMONS, REDIS
+ }
+
+ static class WithEnumValue {
+
+ SpringDataEnum value;
+
+ public SpringDataEnum getValue() {
+ return value;
+ }
+
+ public void setValue(SpringDataEnum value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ WithEnumValue that = (WithEnumValue) o;
+ return value == that.value;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value);
+ }
+ }
+}
diff --git a/src/test/java/org/springframework/data/redis/support/collections/CollectionTestParams.java b/src/test/java/org/springframework/data/redis/support/collections/CollectionTestParams.java
index 8b9063497f..f43926aeed 100644
--- a/src/test/java/org/springframework/data/redis/support/collections/CollectionTestParams.java
+++ b/src/test/java/org/springframework/data/redis/support/collections/CollectionTestParams.java
@@ -31,6 +31,7 @@
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.Jackson3JsonRedisSerializer;
import org.springframework.data.redis.serializer.OxmSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.data.redis.test.XstreamOxmSerializerSingleton;
@@ -48,6 +49,7 @@ public static Collection testParams() {
OxmSerializer serializer = XstreamOxmSerializerSingleton.getInstance();
Jackson2JsonRedisSerializer jackson2JsonSerializer = new Jackson2JsonRedisSerializer<>(Person.class);
+ Jackson3JsonRedisSerializer jackson3JsonSerializer = new Jackson3JsonRedisSerializer<>(Person.class);
StringRedisSerializer stringSerializer = StringRedisSerializer.UTF_8;
// create Jedis Factory
@@ -86,6 +88,12 @@ public static Collection testParams() {
rawTemplate.setKeySerializer(stringSerializer);
rawTemplate.afterPropertiesSet();
+ // jackson3
+ RedisTemplate jackson3JsonPersonTemplate = new RedisTemplate<>();
+ jackson3JsonPersonTemplate.setConnectionFactory(jedisConnFactory);
+ jackson3JsonPersonTemplate.setValueSerializer(jackson3JsonSerializer);
+ jackson3JsonPersonTemplate.afterPropertiesSet();
+
// Lettuce
LettuceConnectionFactory lettuceConnFactory = LettuceConnectionFactoryExtension
.getConnectionFactory(RedisStanalone.class);
@@ -110,6 +118,11 @@ public static Collection testParams() {
jackson2JsonPersonTemplateLtc.setConnectionFactory(lettuceConnFactory);
jackson2JsonPersonTemplateLtc.afterPropertiesSet();
+ RedisTemplate jackson3JsonPersonTemplateLtc = new RedisTemplate<>();
+ jackson3JsonPersonTemplateLtc.setValueSerializer(jackson3JsonSerializer);
+ jackson3JsonPersonTemplateLtc.setConnectionFactory(lettuceConnFactory);
+ jackson3JsonPersonTemplateLtc.afterPropertiesSet();
+
RedisTemplate rawTemplateLtc = new RedisTemplate<>();
rawTemplateLtc.setConnectionFactory(lettuceConnFactory);
rawTemplateLtc.setEnableDefaultSerializer(false);
@@ -122,6 +135,7 @@ public static Collection testParams() {
{ stringFactory, xstreamStringTemplate }, //
{ personFactory, xstreamPersonTemplate }, //
{ personFactory, jackson2JsonPersonTemplate }, //
+ { personFactory, jackson3JsonPersonTemplate }, //
{ rawFactory, rawTemplate },
// lettuce
@@ -132,6 +146,7 @@ public static Collection testParams() {
{ stringFactory, xstreamStringTemplateLtc }, //
{ personFactory, xstreamPersonTemplateLtc }, //
{ personFactory, jackson2JsonPersonTemplateLtc }, //
+ { personFactory, jackson3JsonPersonTemplateLtc }, //
{ rawFactory, rawTemplateLtc } });
}
}
diff --git a/src/test/java/org/springframework/data/redis/support/collections/RedisPropertiesIntegrationTests.java b/src/test/java/org/springframework/data/redis/support/collections/RedisPropertiesIntegrationTests.java
index be3e627a28..f241a7c480 100644
--- a/src/test/java/org/springframework/data/redis/support/collections/RedisPropertiesIntegrationTests.java
+++ b/src/test/java/org/springframework/data/redis/support/collections/RedisPropertiesIntegrationTests.java
@@ -39,6 +39,7 @@
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.Jackson3JsonRedisSerializer;
import org.springframework.data.redis.serializer.OxmSerializer;
import org.springframework.data.redis.test.XstreamOxmSerializerSingleton;
import org.springframework.data.redis.test.extension.RedisStanalone;
@@ -192,6 +193,9 @@ public static Collection testParams() {
Jackson2JsonRedisSerializer jackson2JsonSerializer = new Jackson2JsonRedisSerializer<>(Person.class);
Jackson2JsonRedisSerializer jackson2JsonStringSerializer = new Jackson2JsonRedisSerializer<>(
String.class);
+ Jackson3JsonRedisSerializer jackson3JsonSerializer = new Jackson3JsonRedisSerializer<>(Person.class);
+ Jackson3JsonRedisSerializer jackson3JsonStringSerializer = new Jackson3JsonRedisSerializer<>(
+ String.class);
// create Jedis Factory
ObjectFactory stringFactory = new StringObjectFactory();
@@ -215,6 +219,13 @@ public static Collection testParams() {
jackson2JsonPersonTemplate.setHashValueSerializer(jackson2JsonStringSerializer);
jackson2JsonPersonTemplate.afterPropertiesSet();
+ RedisTemplate jackson3JsonPersonTemplate = new RedisTemplate<>();
+ jackson3JsonPersonTemplate.setConnectionFactory(jedisConnFactory);
+ jackson3JsonPersonTemplate.setDefaultSerializer(jackson3JsonSerializer);
+ jackson3JsonPersonTemplate.setHashKeySerializer(jackson3JsonSerializer);
+ jackson3JsonPersonTemplate.setHashValueSerializer(jackson3JsonStringSerializer);
+ jackson3JsonPersonTemplate.afterPropertiesSet();
+
// Lettuce
LettuceConnectionFactory lettuceConnFactory = LettuceConnectionFactoryExtension
.getConnectionFactory(RedisStanalone.class, false);