When using GenericJacksonJsonRedisSerializer, enum values are always serialized without type metadata, even when DefaultTyping.NON_FINAL_AND_ENUMS is explicitly configured. This causes ClassCastException during deserialization.
Example
public enum Status {
ACTIVE, INACTIVE
}
@Cacheable(cacheNames = "status", cacheManager = "redisCacheManager")
public Status getStatus(String id) {
return Status.ACTIVE;
}
Serialized value in Redis:
Exception on deserialization:
java.lang.ClassCastException: class java.lang.String cannot be cast to class Status
Configuration Attempt
GenericJacksonJsonRedisSerializer.create(b -> {
b.enableDefaultTyping(ptv);
b.customize(mb ->
mb.activateDefaultTypingAsProperty(
ptv,
DefaultTyping.NON_FINAL_AND_ENUMS,
"@class"
)
);
});
Using customize() to override with Jackson's default DefaultTypeResolverBuilder does enable enum type metadata, but it loses Spring's Kotlin-specific handling for final classes:
// This Kotlin support in Spring's TypeResolverBuilder is lost:
if (javaType.isFinal() && !KotlinDetector.isKotlinType(javaType.getRawClass())
&& javaType.getRawClass().getPackageName().startsWith("java")) {
return false;
}
This forces users to choose between enum support and Kotlin data class support.
Root Cause
Spring's TypeResolverBuilder unconditionally excludes enum types regardless of the DefaultTyping setting:
https://github.com/spring-projects/spring-data-redis/blob/main/src/main/java/org/springframework/data/redis/serializer/GenericJacksonJsonRedisSerializer.java#L631-L633
if (javaType.isEnumType() || ClassUtils.isPrimitiveOrWrapper(javaType.getRawClass())) {
return false; // Always excluded
}
Suggested Fix
Store the DefaultTyping parameter and use it in useForType():
private static class TypeResolverBuilder extends DefaultTypeResolverBuilder {
private final DefaultTyping defaultTyping;
public TypeResolverBuilder(PolymorphicTypeValidator subtypeValidator, DefaultTyping defaultTyping,
JsonTypeInfo.As includeAs, JsonTypeInfo.Id idType, @Nullable String propertyName) {
super(subtypeValidator, defaultTyping, includeAs, idType, propertyName);
this.defaultTyping = defaultTyping;
}
@Override
public boolean useForType(JavaType javaType) {
if (javaType.isJavaLangObject()) {
return true;
}
javaType = resolveArrayOrWrapper(javaType);
// Respect DefaultTyping.NON_FINAL_AND_ENUMS for enum types
if (javaType.isEnumType()) {
return defaultTyping == DefaultTyping.NON_FINAL_AND_ENUMS;
}
if (ClassUtils.isPrimitiveOrWrapper(javaType.getRawClass())) {
return false;
}
if (javaType.isFinal() && !KotlinDetector.isKotlinType(javaType.getRawClass())
&& javaType.getRawClass().getPackageName().startsWith("java")) {
return false;
}
return !TreeNode.class.isAssignableFrom(javaType.getRawClass());
}
// ... resolveArrayOrWrapper() unchanged
}
This preserves backward compatibility (enums excluded by default with NON_FINAL) while allowing enum type metadata when explicitly requested via NON_FINAL_AND_ENUMS.
This preserves backward compatibility while allowing enum type metadata when explicitly requested.
Current Workarounds
- Wrap enums in a generic container class
- Copy and modify Spring's
TypeResolverBuilder (not maintainable)
Environment
- Spring Data Redis: 4.0.x
- Jackson: 3.x
When using
GenericJacksonJsonRedisSerializer, enum values are always serialized without type metadata, even whenDefaultTyping.NON_FINAL_AND_ENUMSis explicitly configured. This causesClassCastExceptionduring deserialization.Example
Serialized value in Redis:
"ACTIVE"Exception on deserialization:
Configuration Attempt
Using
customize()to override with Jackson's defaultDefaultTypeResolverBuilderdoes enable enum type metadata, but it loses Spring's Kotlin-specific handling for final classes:This forces users to choose between enum support and Kotlin data class support.
Root Cause
Spring's
TypeResolverBuilderunconditionally excludes enum types regardless of theDefaultTypingsetting:https://github.com/spring-projects/spring-data-redis/blob/main/src/main/java/org/springframework/data/redis/serializer/GenericJacksonJsonRedisSerializer.java#L631-L633
Suggested Fix
Store the
DefaultTypingparameter and use it inuseForType():This preserves backward compatibility (enums excluded by default with
NON_FINAL) while allowing enum type metadata when explicitly requested viaNON_FINAL_AND_ENUMS.This preserves backward compatibility while allowing enum type metadata when explicitly requested.
Current Workarounds
TypeResolverBuilder(not maintainable)Environment