Search before asking
- [x] I searched in the issues and found nothing similar.
Describe the bug
Java Records fail to include @class type information when using DefaultTyping.NON_FINAL, causing Redis caching deserialization failures in Spring Boot applications.
Version Information
2.17.3
Reproduction
@Configuration
public class RedisConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer(createRedisObjectMapper())));
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(cacheConfiguration)
.build();
}
private static ObjectMapper createRedisObjectMapper() {
final ObjectMapper mapper = new ObjectMapper();
mapper.activateDefaultTyping(
mapper.getPolymorphicTypeValidator(),
DefaultTyping.NON_FINAL, // Records are excluded here
JsonTypeInfo.As.PROPERTY
);
return mapper;
}
}
public record UserRecord(Long id, String name) {}
@Service
public class UserService {
@Cacheable("users")
public UserRecord getUser(Long id) {
return new UserRecord(id, "User-" + id);
}
}
@Test
void testRecordCaching() {
UserRecord user1 = userService.getUser(1L); // ✅ Works
UserRecord user2 = userService.getUser(1L); // ❌ Fails
}
exception message: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve subtype of [simple type, class java.lang.Object]: missing type id property '@class'
Expected behavior
No response
Additional context
Related to Previous Issue #3512:
This issue was previously reported in #3512 and closed with the following recommendations: - Use DefaultTyping.EVERYTHING instead of NON_FINAL for Records - Consider using regular classes instead of Records
However, these solutions have limitations:
DefaultTyping.EVERYTHING concerns: - Planned for deprecation and removal in Jackson 3.0 (#4160) - Includes unnecessary type metadata for all fields, raising security concerns - No clear migration path for Jackson 3.0
"Avoid Records" approach issues: - High risk of human error (developers forgetting which pattern to use)
Thank you for your consideration.
Comment From: JooHyukKim
First, it would help alot if you can provide a reproduction with only-Jackson? Something like this from our existing test suite would suffice.
Second, DefaultTyping.NON_FINAL
does really mean non-final types, as per documentation...
... so Java Record not being included in DefaultTyping.NON_FINAL
configuration is a natural behavior.
Also, there is another solution suggested -- check out the thread and you will find most answers @cndqjacndqja .
Comment From: cndqjacndqja
@JooHyukKim First of all, thank you for the quick response
I believe NON_FINAL excludes Records because they are final classes that don't support polymorphism, assuming that @class type information wouldn't be needed during deserialization. Reference documentation
The issue occurs in the following case:
public class JacksonTest {
@Test
public void testRecordDeserialization() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.activateDefaultTyping(
mapper.getPolymorphicTypeValidator(),
DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY
);
User user = new User(1L, "test-user");
String json = mapper.writeValueAsString(user);
// This fails: missing @class property
Object result = mapper.readValue(json, Object.class);
}
}
record User(Long id, String name) {
}
When running this test code, the following error occurs:
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve subtype of [simple type, class java.lang.Object]: missing type id property '@class'
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 27]
This appears to be the same issue that occurs when GenericJackson2JsonRedisSerializer uses ObjectMapper. GenericJackson2JsonRedisSerializer deserializes using ObjectMapper as follows:
@Override
public @Nullable Object deserialize(byte @Nullable[] source) throws SerializationException {
return deserialize(source, Object.class);
}
GenericJackson2JsonRedisSerializer
Since it deserializes to Object.class, Records fail due to missing @class type information. Therefore, as shown in the original issue example, when implementing Redis caching using GenericJackson2JsonRedisSerializer, caching Records results in errors.
My question is: Should this be considered a problem with how GenericJackson2JsonRedisSerializer uses ObjectMapper? Was ObjectMapper originally designed with the expectation that classes without polymorphism (like Records or final classes) should NOT be deserialized to Object.class?
Thank you for your time and consideration.