# How to Handle Cache Serialization with Different Codecs in CoCache

> Learn to handle cache serialization with different codecs in CoCache. Explore built-in JSON, Redis Hash, and custom binary formats using CodecExecutor.

- Repository: [Ahoo Wang/cocache](https://github.com/ahoo-wang/cocache)
- Tags: how-to-guide
- Published: 2026-02-23

---

**CoCache abstracts cache value serialization through the `CodecExecutor<V>` interface, allowing you to configure built-in strategies like JSON or Redis Hash encoding, or implement custom binary formats by extending `AbstractCodecExecutor`.**

CoCache (ahoo-wang/cocache) provides a flexible, pluggable serialization layer that decouples your domain objects from Redis storage formats. Whether you need human-readable JSON for debugging or compact binary serialization for high-performance scenarios, understanding how to handle cache serialization with different codecs in CoCache enables you to optimize both storage efficiency and read/write performance.

## The CodecExecutor Contract

At the core of CoCache’s serialization strategy lies the **`CodecExecutor<V>`** interface defined in [`cocache-spring-redis/src/main/kotlin/me/ahoo/cache/spring/redis/codec/CodecExecutor.kt`](https://github.com/ahoo-wang/cocache/blob/main/cocache-spring-redis/src/main/kotlin/me/ahoo/cache/spring/redis/codec/CodecExecutor.kt). This contract standardizes how cache values are transformed before persistence and reconstructed upon retrieval.

The interface specifies two essential operations:

```kotlin
interface CodecExecutor<V> {
    /** Decode the cached entry back to a CacheValue */
    fun executeAndDecode(key: String, ttlAt: Long): CacheValue<V>

    /** Encode the CacheValue before persisting */
    fun executeAndEncode(key: String, cacheValue: CacheValue<V>)
}

```

The `executeAndEncode` method receives the cache key and the `CacheValue<V>` wrapper containing your domain object and TTL metadata, allowing you to serialize the value into the format required by your storage backend. Conversely, `executeAndDecode` handles deserialization using the key and expiration timestamp.

## Built-in Serialization Strategies

CoCache ships with five pre-built executors in the `cocache-spring-redis` module, each optimized for specific data types and access patterns:

- **StringToStringCodecExecutor** ([`StringToStringCodecExecutor.kt`](https://github.com/ahoo-wang/cocache/blob/main/StringToStringCodecExecutor.kt)): Passes string values through without transformation. Use this when caching raw text content like Markdown or pre-serialized JSON strings.

- **ObjectToJsonCodecExecutor** ([`ObjectToJsonCodecExecutor.kt`](https://github.com/ahoo-wang/cocache/blob/main/ObjectToJsonCodecExecutor.kt)): Converts POJOs to JSON using Jackson. This is the default choice for most domain objects, providing human-readable storage and easy debugging.

- **ObjectToHashCodecExecutor** ([`ObjectToHashCodecExecutor.kt`](https://github.com/ahoo-wang/cocache/blob/main/ObjectToHashCodecExecutor.kt)): Maps object properties to Redis hash fields. This enables partial updates and field-level TTL management rather than rewriting entire objects.

- **MapToHashCodecExecutor**: Persists `Map<K,V>` structures directly as Redis hashes, ideal for dictionary-like cache entries.

- **SetToSetCodecExecutor**: Stores `Set<E>` collections using native Redis set commands, preserving set semantics and supporting set-based operations like intersections.

All concrete implementations extend **`AbstractCodecExecutor`** ([`AbstractCodecExecutor.kt`](https://github.com/ahoo-wang/cocache/blob/main/AbstractCodecExecutor.kt)), which provides common Redis helper methods and template operations to simplify custom implementations.

## Configuring Codecs in RedisDistributedCache

When constructing a **`RedisDistributedCache`** (or using `RedisDistributedCacheFactory`), you inject the desired executor via the constructor. This design allows different cache instances within the same application to use different serialization strategies.

To configure JSON serialization for a `User` object cache:

```kotlin
@Bean
fun userCache(redisTemplate: RedisTemplate<String, String>): DistributedCache<User> {
    val codecExecutor = ObjectToJsonCodecExecutor<User>()
    return RedisDistributedCache(redisTemplate, codecExecutor)
}

```

To switch to hash-based storage for the same object type:

```kotlin
val codecExecutor = ObjectToHashCodecExecutor<User>()
return RedisDistributedCache(redisTemplate, codecExecutor)

```

The `RedisDistributedCache` class delegates all serialization concerns to the supplied executor, keeping the cache implementation agnostic of the actual storage format.

## Implementing Custom Codecs

For proprietary binary formats or third-party serializers like **Kryo** or **Protobuf**, extend `AbstractCodecExecutor` and implement the two abstract methods. Here is a complete Kryo implementation:

```kotlin
class KryoCodecExecutor<V : Any>(private val kryo: Kryo) : AbstractCodecExecutor<V>() {
    override fun executeAndEncode(key: String, cacheValue: CacheValue<V>) {
        val bytes = ByteArrayOutputStream().use { bos ->
            kryo.writeObject(bos, cacheValue.value)
            bos.toByteArray()
        }
        redisTemplate.opsForValue().set(
            key, 
            bytes, 
            cacheValue.ttlAt, 
            TimeUnit.SECONDS
        )
    }

    override fun executeAndDecode(key: String, ttlAt: Long): CacheValue<V> {
        val bytes = redisTemplate.opsForValue().get(key) 
            ?: return CacheValue.empty()
        val value = ByteArrayInputStream(bytes).use { bis -> 
            kryo.readObject(bis, V::class.java) 
        }
        return CacheValue(value, ttlAt)
    }
}

```

Register the custom executor as you would a built-in one:

```kotlin
@Bean
fun userCacheKryo(redisTemplate: RedisTemplate<String, ByteArray>): DistributedCache<User> {
    val kryo = Kryo().apply { register(User::class.java) }
    val codecExecutor = KryoCodecExecutor<User>(kryo)
    return RedisDistributedCache(redisTemplate, codecExecutor)
}

```

## Summary

- **CodecExecutor<V>** defines the contract for encoding and decoding cache values in [`CodecExecutor.kt`](https://github.com/ahoo-wang/cocache/blob/main/CodecExecutor.kt).
- Choose **ObjectToJsonCodecExecutor** for standard POJO serialization, **ObjectToHashCodecExecutor** for partial updates, or **StringToStringCodecExecutor** for raw text.
- Supply your selected executor to `RedisDistributedCache` or `RedisDistributedCacheFactory` to wire the serialization strategy.
- Extend **AbstractCodecExecutor** to implement custom binary codecs using libraries like Kryo or Protobuf.

## Frequently Asked Questions

### What is the default codec for object serialization in CoCache?

The **ObjectToJsonCodecExecutor** serves as the default strategy for most domain objects, utilizing Jackson to map POJOs to JSON strings. This provides a balance of human readability and broad compatibility across different services and languages.

### Can different cache instances use different codecs in the same application?

Yes. Because the codec is injected into `RedisDistributedCache` via its constructor, you can declare multiple cache beans—each configured with a different `CodecExecutor` implementation. One cache might use JSON for debugging while another uses Kryo for performance-critical data.

### Which codec supports partial updates of cached objects?

The **ObjectToHashCodecExecutor** stores each object property as a separate Redis hash field, enabling you to update individual fields without rewriting the entire cache entry. This also supports field-level TTL configurations that are not possible with string-based serialization.

### How do I handle schema evolution when using custom binary codecs?

When implementing custom executors by extending `AbstractCodecExecutor`, you control the serialization format entirely. For schema evolution with binary formats like Kryo or Protobuf, register versioned serializers within your codec’s initialization logic, or implement a migration strategy in the `executeAndDecode` method to handle legacy formats gracefully.