A lightweight, high-performance caching library for Kotlin
- Simple and Intuitive DSL - Readable API leveraging Kotlin's language features
- Flexible Eviction Strategies - Built-in LRU, FIFO, and custom strategies
- Automatic Expiration Management - TTL-based automatic entry removal
- Lightweight Design - Only external dependency is SLF4J
- Thread-Safe - Safe to use in multi-threaded environments
- Null Value Support - Optional support for caching null values
dependencies {
implementation("net.ririfa:cask:[version]")
}dependencies {
implementation 'net.ririfa:cask:[version]'
}<dependency>
<groupId>net.ririfa</groupId>
<artifactId>cask</artifactId>
<version>[version]</version>
</dependency>import net.ririfa.cask.cask
// Create a cache with minimal configuration
val cache = cask<String, String> {
loader { key ->
// Load data from database or API
fetchFromDatabase(key)
}
}
// Get data from cache (loader will be invoked if not present)
val value = cache.get("user:123")
// Manually add data
cache.put("user:456", userData)
// Invalidate an entry
cache.invalidate("user:123")
// Refresh data
cache.refresh("user:123")val cache = cask<String, User> {
// TTL configuration (default: 10 minutes)
ttlMinutes = 30 // 30 minutes
// or
ttlSeconds = 1800
ttlHours = 1
// Maximum size (default: 100)
maxSize = 1000
// Data loader (required)
loader { userId ->
userRepository.findById(userId)
}
// Eviction callback (optional)
onEvict { key, value ->
logger.info("Evicted: $key")
}
// Allow null values (default: false)
allowNullValues()
// LRU strategy (default)
lru = true
}Cask provides 2 built-in eviction strategies:
Removes entries that haven't been accessed for the longest time.
val cache = cask<String, String> {
lru = true
loader { key -> loadData(key) }
}Removes the oldest entries first.
val cache = cask<String, String> {
fifo = true
loader { key -> loadData(key) }
}Implement your own eviction logic:
import net.ririfa.cask.EvictionStrategy
import net.ririfa.cask.util.EvictionPolicy
val cache = cask<String, String> {
evictionPolicy(EvictionPolicy.CUSTOM)
evictionStrategy(object : EvictionStrategy<String, String> {
override fun shouldEvict(
cache: Map<String, String>,
key: String,
value: String
): Boolean {
// Custom logic
return value.length > 1000
}
})
loader { key -> loadData(key) }
}import java.util.concurrent.TimeUnit
val cache = cask<String, String> {
ttl(5, TimeUnit.HOURS)
loader { key -> loadData(key) }
}import java.util.concurrent.Executors
val customExecutor = Executors.newScheduledThreadPool(2)
val cache = cask<String, String> {
withCustomGcExecutor(customExecutor)
shareGcExecutor(false)
loader { key -> loadData(key) }
}
// On application shutdown
customExecutor.shutdown()Share a single GC thread across multiple cache instances:
val cache1 = cask<String, String> {
shareGcExecutor(true) // default
loader { key -> loadData(key) }
}
val cache2 = cask<Int, User> {
shareGcExecutor(true)
loader { id -> loadUser(id) }
}
// On application shutdown
CaskRuntime.shutdown()- Keep Loader Functions Lightweight - Avoid heavy processing in loaders; use additional cache layers if needed
- Set Appropriate TTL - Adjust TTL based on data update frequency
- Configure maxSize Properly - Balance between memory usage and cache hit rate
- Utilize Eviction Callbacks - Use
onEvictfor debugging and metrics collection - Share GC Executor - Use shared GC executor when you have multiple cache instances
- Memory Efficient: Minimized memory usage with lightweight internal data structures
- Thread-Safe: High throughput with minimal synchronization blocks
- GC Optimized: Background thread periodically cleans up expired entries (every 5 seconds)
class UserService(private val userRepository: UserRepository) {
private val userCache = cask<Long, User> {
ttlMinutes = 15
maxSize = 5000
lru = true
loader { userId ->
userRepository.findById(userId)
}
onEvict { userId, user ->
logger.debug("User cache evicted: $userId")
}
}
fun getUser(userId: Long): User? = userCache.get(userId)
fun updateUser(user: User) {
userRepository.save(user)
userCache.invalidate(user.id)
}
}class ApiClient {
private val responseCache = cask<String, ApiResponse> {
ttlMinutes = 5
maxSize = 500
loader { endpoint ->
httpClient.get(endpoint).body()
}
}
suspend fun fetchData(endpoint: String): ApiResponse? {
return responseCache.get(endpoint)
}
}class Calculator {
private val resultCache = cask<Pair<Int, Int>, BigDecimal> {
ttlHours = 24
maxSize = 10000
fifo = true
loader { (a, b) ->
expensiveCalculation(a, b)
}
}
fun calculate(a: Int, b: Int): BigDecimal? {
return resultCache.get(a to b)
}
}This project is licensed under the MIT License. See the LICENSE file for details.
Pull requests and issue reports are welcome!