spring-projects / spring-projects/spring-data-redis

`RedisCache` with `GenericJacksonJsonRedisSerializer` breaks when using enums

Open
#3,306 4 comments 1 reaction 1 assignee View on GitHub

@mp911de is already working on this.

Since Feb 9, 2026.

  • #3309 by @mp911de — open
type: bug
Dominant language
Java
Stars
1.9k
Forks
1.3k
PR merge metrics
No merged PRs in 30d

Description

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:

"ACTIVE"

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
  1. Wrap enums in a generic container class
  2. Copy and modify Spring's TypeResolverBuilder (not maintainable)
Environment
  • Spring Data Redis: 4.0.x
  • Jackson: 3.x

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.