ktor-persistent-cache

Persistent HTTP caching for Ktor HttpClient with disk-backed storage, configurable TTL and max size, LRU eviction, Vary-header aware variants, and optional custom cache-directory provider.

Android JVMJVMKotlin/Native
GitHub stars24
Open issues0
Creation date3 months ago

Last activity7 days ago
Latest release1.0.1 (29 days ago)

Ktor Persistent Cache

Kotlin Kotlin Multiplatform Ktor Android iOS JVM

A Kotlin Multiplatform library that adds persistent HTTP caching to Ktor HttpClient. Responses are stored on disk using Okio, with configurable size limits, TTL, and platform-appropriate cache directories.


Features

  • Persistent storage — Cache survives app restarts; stored on the filesystem via Okio.
  • Kotlin Multiplatform — Shared API for Android, iOS, and JVM.
  • Ktor integration — Uses Ktor’s HttpCache plugin; you configure cache storage and options in one place.
  • Configurable — TTL (time-to-live), max cache size, directory name, shared vs unshared, and public vs private storage.
  • Shared API surfaceCacheStorageConfig models directory name, max size, and TTL; CacheStorageFactory builds Ktor Cache Storage (CacheStorage) with Okio persistence — the same backing store installPersistentCache uses when enabled is true.
  • Content negotiation — Respects Vary headers so different variants (e.g. by Accept-Language) are cached separately.
  • LRU eviction — When the cache exceeds the configured size, least-recently-used entries are removed.
  • Custom cache location — Optional CacheDirectoryProvider for custom cache root paths (e.g. for tests or special directories).

Supported platforms

Platform Cache directory
Android Application cache dir (context.cacheDir)
iOS App caches directory (NSCachesDirectory in the sandbox)
JVM java.io.tmpdir/ktor-cache

Requirements

  • Kotlin 2.3.0+
  • Ktor HttpClient (e.g. ktor-client-core 3.4.0+) and an engine (CIO, OkHttp, etc.) for your targets
  • Android: minSdk 24+, JDK 11+
  • iOS: Standard deployment targets
  • JVM: JDK 11+

Installation

Add the dependency to your shared or platform source sets.

Kotlin DSL (Gradle):

repositories {
    mavenCentral()
    // For snapshots:
    // maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
}

dependencies {
    commonMain.dependencies {
        implementation("io.github.santimattius:ktor-persistent-cache:1.0.0-SNAPSHOT")
    }
    // Also add a Ktor engine for each target, e.g.:
    // implementation("io.ktor:ktor-client-okhttp")   // Android
    // implementation("io.ktor:ktor-client-cio")     // iOS / JVM
}

Version catalog (e.g. libs.versions.toml):

[versions]
ktorPersistentCache = "1.0.0-SNAPSHOT"

[libraries]
ktor-persistent-cache = { group = "io.github.santimattius", name = "ktor-persistent-cache", version.ref = "ktorPersistentCache" }

Then:

implementation(libs.ktor.persistent.cache)

Setup

Android

The library needs the application context to resolve the cache directory. The recommended way is App Startup:

  1. Merge the library’s manifest
    The shared (or Android) module that depends on ktor-persistent-cache should merge the library’s AndroidManifest so that the App Startup InitializationProvider and ContextInitializer are registered.

  2. No extra code
    If the manifest is merged, ContextInitializer runs at app startup and injects the application context. getCacheDirectoryProvider() will then use it automatically.

If you don’t use the library’s manifest (e.g. you use a different DI or startup path), you must call once at app startup with the application context (not an Activity context):

// e.g. in Application.onCreate()
injectContext(applicationContext)

injectContext is provided by the library package io.github.santimattius.persistent.cache.startup.

iOS

No setup. The library uses the default app caches directory.

JVM

No setup. The library uses a subdirectory of the JVM temp directory.


Quick start

  1. Create an HttpClient and call installPersistentCache with a CacheConfig:
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.github.santimattius.persistent.cache.*

val client = HttpClient(CIO) {
    installPersistentCache(
        CacheConfig(
            enabled = true,
            cacheDirectory = "http_cache",
            maxCacheSize = 10L * 1024 * 1024, // 10 MB
            cacheTtl = 60 * 60 * 1000,       // 1 hour
            isShared = true,
            isPublic = false
        )
    )
}
  1. Use the client as usual. The cache stores responses for requests that support caching and serves them when valid.
val response: String = client.get("https://example.com/api/data").body()
  1. Optionally pass a custom CacheDirectoryProvider as the second parameter ( see Custom cache directory).

Configuration

CacheConfig supports:

Property Type Default Description
enabled Boolean false Whether the HTTP cache is enabled.
cacheDirectory String "http_cache" Name of the cache directory under the platform cache root.
maxCacheSize Long 10 MB Maximum cache size in bytes. LRU eviction when exceeded. Use 0 for no limit.
cacheTtl Long 1 hour Time-to-live for entries in milliseconds.
isShared Boolean true Whether the cache is shared across requests (Ktor HttpCache behavior).
isPublic Boolean false When true, cached responses are treated as public (shareable across users); when false, they are private to the client.

Convenience constructor:

CacheConfig(enabled = true, cacheDirectory = "my_cache")
// Other properties use defaults.

Shared configuration and CacheStorageFactory

CacheStorageConfig covers only persisted disk knobs: subdirectory name under the platform (or custom) cache root, maximum size in bytes, and TTL in milliseconds.

CacheConfig implements that shape and adds enabled, isShared, and isPublic, which installPersistentCache respects alongside HttpCache.

CacheStorageFactory produces Okio-backed CacheStorage. With enabled == true, installPersistentCache delegates here; you can call the factory directly when configuring HttpCache yourself—for example with a fake filesystem or deterministic clock in tests:

val storage = CacheStorageFactory.create(
    config = CacheConfig(
        enabled = true, // Ignored by the factory; use installPersistentCache to honor `enabled`
        cacheDirectory = "http_cache",
        maxCacheSize = 10L * 1024 * 1024,
        cacheTtl = 60 * 60 * 1000,
    ),
    cacheDirectoryProvider = getCacheDirectoryProvider()
)

HttpClient(CIO) {
    install(HttpCache) {
        isShared = true
        privateStorage(storage)
    }
}

Important: Only installPersistentCache maps CacheConfig.enabled == false to CacheStorage.Disabled. If you bypass it and always call CacheStorageFactory.create, you construct disk storage explicitly; pair with your own HttpCache setup (or omit the plugin if you don’t want HTTP caching behavior).


Custom cache directory

To control where the cache is stored (e.g. a custom folder or test directory), implement CacheDirectoryProvider and pass it to installPersistentCache:

val customProvider = object : CacheDirectoryProvider {
    override val cacheDirectory: Path get() = FileSystem.SYSTEM.toPath("/custom/cache/dir")
}

HttpClient(CIO) {
    installPersistentCache(
        config = CacheConfig(enabled = true, cacheDirectory = "http_cache"),
        cacheDirectoryProvider = customProvider
    )
}

Default behavior (no custom provider): getCacheDirectoryProvider() returns the platform implementation (Android app cache dir, iOS caches dir, or JVM temp dir).


Building and testing (from source)

Build:

./gradlew :shared:compileKotlinIosSimulatorArm64 :shared:compileAndroidMain
# or
./gradlew :shared:assemble

Run tests:

./gradlew test

Publish to local Maven:

./gradlew :shared:publishToMavenLocal

Then depend on io.github.santimattius:ktor-persistent-cache:1.0.0-SNAPSHOT with mavenLocal() in your project.


Publishing

The shared module is published with the gradle-maven-publish-plugin.

Action Command / Doc
Publish to local Maven ./gradlew :shared:publishToMavenLocal
Publish to Maven Central See docs/PUBLISHING.md for credentials and steps.

Coordinates and POM are configured in shared/build.gradle.kts.


License

This project is licensed under the Apache License, Version 2.0.


References

Resource URL
Ktor — HTTP client ktor.io/docs/client
Ktor — Caching ktor.io/docs/client-caching
Okio github.com/square/okio
Kotlin Multiplatform kotlinlang.org/docs/multiplatform
Publishing (this repo) docs/PUBLISHING.md
Android JVMJVMKotlin/Native
GitHub stars24
Open issues0
Creation date3 months ago

Last activity7 days ago
Latest release1.0.1 (29 days ago)

Ktor Persistent Cache

Kotlin Kotlin Multiplatform Ktor Android iOS JVM

A Kotlin Multiplatform library that adds persistent HTTP caching to Ktor HttpClient. Responses are stored on disk using Okio, with configurable size limits, TTL, and platform-appropriate cache directories.


Features

  • Persistent storage — Cache survives app restarts; stored on the filesystem via Okio.
  • Kotlin Multiplatform — Shared API for Android, iOS, and JVM.
  • Ktor integration — Uses Ktor’s HttpCache plugin; you configure cache storage and options in one place.
  • Configurable — TTL (time-to-live), max cache size, directory name, shared vs unshared, and public vs private storage.
  • Shared API surfaceCacheStorageConfig models directory name, max size, and TTL; CacheStorageFactory builds Ktor Cache Storage (CacheStorage) with Okio persistence — the same backing store installPersistentCache uses when enabled is true.
  • Content negotiation — Respects Vary headers so different variants (e.g. by Accept-Language) are cached separately.
  • LRU eviction — When the cache exceeds the configured size, least-recently-used entries are removed.
  • Custom cache location — Optional CacheDirectoryProvider for custom cache root paths (e.g. for tests or special directories).

Supported platforms

Platform Cache directory
Android Application cache dir (context.cacheDir)
iOS App caches directory (NSCachesDirectory in the sandbox)
JVM java.io.tmpdir/ktor-cache

Requirements

  • Kotlin 2.3.0+
  • Ktor HttpClient (e.g. ktor-client-core 3.4.0+) and an engine (CIO, OkHttp, etc.) for your targets
  • Android: minSdk 24+, JDK 11+
  • iOS: Standard deployment targets
  • JVM: JDK 11+

Installation

Add the dependency to your shared or platform source sets.

Kotlin DSL (Gradle):

repositories {
    mavenCentral()
    // For snapshots:
    // maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
}

dependencies {
    commonMain.dependencies {
        implementation("io.github.santimattius:ktor-persistent-cache:1.0.0-SNAPSHOT")
    }
    // Also add a Ktor engine for each target, e.g.:
    // implementation("io.ktor:ktor-client-okhttp")   // Android
    // implementation("io.ktor:ktor-client-cio")     // iOS / JVM
}

Version catalog (e.g. libs.versions.toml):

[versions]
ktorPersistentCache = "1.0.0-SNAPSHOT"

[libraries]
ktor-persistent-cache = { group = "io.github.santimattius", name = "ktor-persistent-cache", version.ref = "ktorPersistentCache" }

Then:

implementation(libs.ktor.persistent.cache)

Setup

Android

The library needs the application context to resolve the cache directory. The recommended way is App Startup:

  1. Merge the library’s manifest
    The shared (or Android) module that depends on ktor-persistent-cache should merge the library’s AndroidManifest so that the App Startup InitializationProvider and ContextInitializer are registered.

  2. No extra code
    If the manifest is merged, ContextInitializer runs at app startup and injects the application context. getCacheDirectoryProvider() will then use it automatically.

If you don’t use the library’s manifest (e.g. you use a different DI or startup path), you must call once at app startup with the application context (not an Activity context):

// e.g. in Application.onCreate()
injectContext(applicationContext)

injectContext is provided by the library package io.github.santimattius.persistent.cache.startup.

iOS

No setup. The library uses the default app caches directory.

JVM

No setup. The library uses a subdirectory of the JVM temp directory.


Quick start

  1. Create an HttpClient and call installPersistentCache with a CacheConfig:
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.github.santimattius.persistent.cache.*

val client = HttpClient(CIO) {
    installPersistentCache(
        CacheConfig(
            enabled = true,
            cacheDirectory = "http_cache",
            maxCacheSize = 10L * 1024 * 1024, // 10 MB
            cacheTtl = 60 * 60 * 1000,       // 1 hour
            isShared = true,
            isPublic = false
        )
    )
}
  1. Use the client as usual. The cache stores responses for requests that support caching and serves them when valid.
val response: String = client.get("https://example.com/api/data").body()
  1. Optionally pass a custom CacheDirectoryProvider as the second parameter ( see Custom cache directory).

Configuration

CacheConfig supports:

Property Type Default Description
enabled Boolean false Whether the HTTP cache is enabled.
cacheDirectory String "http_cache" Name of the cache directory under the platform cache root.
maxCacheSize Long 10 MB Maximum cache size in bytes. LRU eviction when exceeded. Use 0 for no limit.
cacheTtl Long 1 hour Time-to-live for entries in milliseconds.
isShared Boolean true Whether the cache is shared across requests (Ktor HttpCache behavior).
isPublic Boolean false When true, cached responses are treated as public (shareable across users); when false, they are private to the client.

Convenience constructor:

CacheConfig(enabled = true, cacheDirectory = "my_cache")
// Other properties use defaults.

Shared configuration and CacheStorageFactory

CacheStorageConfig covers only persisted disk knobs: subdirectory name under the platform (or custom) cache root, maximum size in bytes, and TTL in milliseconds.

CacheConfig implements that shape and adds enabled, isShared, and isPublic, which installPersistentCache respects alongside HttpCache.

CacheStorageFactory produces Okio-backed CacheStorage. With enabled == true, installPersistentCache delegates here; you can call the factory directly when configuring HttpCache yourself—for example with a fake filesystem or deterministic clock in tests:

val storage = CacheStorageFactory.create(
    config = CacheConfig(
        enabled = true, // Ignored by the factory; use installPersistentCache to honor `enabled`
        cacheDirectory = "http_cache",
        maxCacheSize = 10L * 1024 * 1024,
        cacheTtl = 60 * 60 * 1000,
    ),
    cacheDirectoryProvider = getCacheDirectoryProvider()
)

HttpClient(CIO) {
    install(HttpCache) {
        isShared = true
        privateStorage(storage)
    }
}

Important: Only installPersistentCache maps CacheConfig.enabled == false to CacheStorage.Disabled. If you bypass it and always call CacheStorageFactory.create, you construct disk storage explicitly; pair with your own HttpCache setup (or omit the plugin if you don’t want HTTP caching behavior).


Custom cache directory

To control where the cache is stored (e.g. a custom folder or test directory), implement CacheDirectoryProvider and pass it to installPersistentCache:

val customProvider = object : CacheDirectoryProvider {
    override val cacheDirectory: Path get() = FileSystem.SYSTEM.toPath("/custom/cache/dir")
}

HttpClient(CIO) {
    installPersistentCache(
        config = CacheConfig(enabled = true, cacheDirectory = "http_cache"),
        cacheDirectoryProvider = customProvider
    )
}

Default behavior (no custom provider): getCacheDirectoryProvider() returns the platform implementation (Android app cache dir, iOS caches dir, or JVM temp dir).


Building and testing (from source)

Build:

./gradlew :shared:compileKotlinIosSimulatorArm64 :shared:compileAndroidMain
# or
./gradlew :shared:assemble

Run tests:

./gradlew test

Publish to local Maven:

./gradlew :shared:publishToMavenLocal

Then depend on io.github.santimattius:ktor-persistent-cache:1.0.0-SNAPSHOT with mavenLocal() in your project.


Publishing

The shared module is published with the gradle-maven-publish-plugin.

Action Command / Doc
Publish to local Maven ./gradlew :shared:publishToMavenLocal
Publish to Maven Central See docs/PUBLISHING.md for credentials and steps.

Coordinates and POM are configured in shared/build.gradle.kts.


License

This project is licensed under the Apache License, Version 2.0.


References

Resource URL
Ktor — HTTP client ktor.io/docs/client
Ktor — Caching ktor.io/docs/client-caching
Okio github.com/square/okio
Kotlin Multiplatform kotlinlang.org/docs/multiplatform
Publishing (this repo) docs/PUBLISHING.md