Caching in Kotlin: Redis and Other Solutions
Caching is one of the key mechanisms for optimizing performance in modern applications. In the Kotlin ecosystem, thanks to full compatibility with Java and the rich JVM library base, all major tools for working with cache are available. In this article, we will look at how to organize caching in Kotlin using Redis, Caffeine, and Ehcache, and also analyze when to choose which tool.
Why is caching needed?
Cache allows you to reduce application response time by storing frequently requested data in fast-access memory. This is especially relevant for:
- high-load REST APIs;
- working with external APIs and databases;
- resource-intensive computations;
- user session data.
Basic Caching Strategies
Before choosing a library, it is important to understand the basic strategies:
- Cache-Aside — the application manages the cache itself: it first checks the cache, on a miss loads data from the database and puts it into the cache.
- Read-Through — the cache automatically loads data from the source on a miss.
- Write-Through / Write-Behind — writing data simultaneously (or asynchronously) to the cache and the main storage.
1. Redis from Kotlin
Redis is an in-memory key-value store that is often used as a distributed cache. To work with Redis in Kotlin, the Lettuce client (asynchronous, reactive) or Jedis (synchronous) is used.
Adding Lettuce
// build.gradle.ktsimplementation("io.lettuce:lettuce-core:6.3.2.RELEASE")Example of working with Redis via Lettuce (Kotlin)
import io.lettuce.core.RedisClientimport io.lettuce.core.api.coroutines
suspend fun main() { val client = RedisClient.create("redis://localhost:6379") val connection = client.connect() val commands = connection.coroutines() // coroutine API
// Write commands.setex("user:123:profile", 3600, "{\\"name\\":\\"Alice\\"}") // Read val cached = commands.get("user:123:profile") println("From cache: $cached") connection.close() client.shutdown()}When to use Redis?
- A distributed cache between multiple application instances is needed.
- High fault tolerance and replication are required.
- Data must be available after application restart (Redis persistence).
- Support for complex structures (lists, sets, queues) is necessary.
2. Caffeine — High-Performance Local Cache
Caffeine is a library for in-memory caching on the JVM, written in Java. It is often used in conjunction with Spring Boot, but works great in pure Kotlin as well. Caffeine provides near-zero latency on reads.
Adding Caffeine
// build.gradle.ktsimplementation("com.github.ben-manes.caffeine:caffeine:3.1.8")Example of using Caffeine in Kotlin
import com.github.benmanes.caffeine.cache.Caffeineimport java.util.concurrent.TimeUnit
data class User(val id: Int, val name: String)
fun createCache(): Caffeine<Int, User>.() -> Unit = { maximumSize(10_000) expireAfterWrite(10, TimeUnit.MINUTES) recordStats()}
val userCache = Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(10, TimeUnit.MINUTES) .build<Int, User>()
fun getUserFromCache(id: Int): User? { return userCache.getIfPresent(id)}
fun putUser(id: Int, user: User) { userCache.put(id, user)}
// Example with automatic loading (LoadingCache)val loadingCache = Caffeine.newBuilder() .maximumSize(5_000) .build<Int, User>