Embed static resources directly into your Kotlin binaries
KtEmbed is a Kotlin Multiplatform library and Gradle plugin that lets you embed static resources (text, JSON, HTML, images, etc.) directly into your compiled applications. Resources are encoded as Z85 strings at compile time and can be accessed efficiently at runtime with automatic caching and optimization strategies.
- 🚀 Small Runtime Dependency - Resources are embedded directly in your compiled code, using Okio for reading
- 🔧 Gradle Integration - Simple plugin configuration with automatic code generation
- 🎯 Kotlin Multiplatform - Works on JVM, Native, and other Kotlin targets
- 💾 Smart Caching - Automatic memory and disk caching with configurable strategies
- ⚡ Optimized Access - Choose between speed (in-memory) or memory efficiency (streaming)
Add the KtEmbed plugin to your build.gradle.kts:
plugins {
id("dev.ktool.ktembed") version "0.0.1"
}ktembed {
packageName = "com.example.resources"
resourceDirectories = listOf("src/main/resources")
}import com.example.resources.ResourceDirectory
// Initialize the Resources instance
val resources = ResourceDirectory.toResources()
// Read as string
val config = resources.asString("config/config.json")
// Read as bytes
val image = resources.asBytes("img/logo.png")
// Check if resource exists
if (resources.exists("text/optional.txt")) {
println(resources.asString("text/optional.txt"))
}
// Write to output stream (JVM only)
FileOutputStream("output.png").use { out ->
resources.write("logo.png", out)
}
// Get file path (cached to temp directory)
val filePath = resources.asPath("data.bin")// In settings.gradle.kts
pluginManagement {
repositories {
gradlePluginPortal()
}
}
// In build.gradle.kts
plugins {
id("dev.ktool.ktembed") version "0.1.1"
}
dependencies {
implementation("dev.ktool:ktembed-runtime:0.1.1")
}The ktembed extension provides the following configuration options:
| Property | Type | Required | Description |
|---|---|---|---|
packageName |
String |
Yes | Package name for the generated ResourceDirectory class |
resourceDirectories |
StringList |
Yes | Directories to scan for resources |
exclude |
(String) -> Boolean |
No | Function to exclude paths (returns true to ignore) |
ktembed {
// Package for generated code
packageName = "com.myapp.assets"
// Multiple resource directories
resourceDirectories = listOf(
"src/main/resources",
"assets",
"static"
)
// Filter function to exclude files
exclude = { path ->
path.endsWith(".tmp") || // Ignore temp files
path.contains(".git") || // Ignore git files
path.startsWith("private/") // Ignore private directory
}
}The main API for accessing embedded resources.
Check if a resource exists at the given path.
if (resources.exists("config.json")) {
// Resource exists
}Read resource as UTF-8 string. Result is cached in memory.
val json = resources.asString("data.json")Read resource as bytes. Result is cached in memory.
val imageData = resources.asByteArray("image.png")Get a file system path to the resource. The resource is extracted to a cache directory and validated.
val configFile = resources.asPath("config.properties")
// Use with libraries that need file pathsWrite resource to an Okio Sink. Automatically chooses optimization strategy based on inMemoryCutoff.
FileSystem.SYSTEM.sink(outputPath).use { sink ->
resources.write("large-file.bin", sink)
}Write resource with explicit optimization strategy.
resources.write("data.bin", sink, OptimizationStrategy.Memory)Write resource to a Java OutputStream.
FileOutputStream("output.txt").use { out ->
resources.write("input.txt", out)
}Choose between speed and memory efficiency:
enum class OptimizationStrategy {
Speed, // Load entire resource into memory (faster)
Memory // Stream from disk cache (uses less memory)
}When to use:
Speed: Small resources, frequent access, plenty of memoryMemory: Large resources (>6MB), infrequent access, memory-constrained environments
- Build Time: The Gradle plugin scans your resource directories and generates Kotlin code with Z85-encoded resources
- Compile Time: Resources are compiled directly into your application binary
- Runtime: Resources accessed through
Resourcesclass and are lazily decoded and cached as needed
- Resources are split into chunks (to avoid JVM string literal limits)
- Each chunk is zipped and Z85-encoded
- Chunks are stored as string literals in generated Kotlin code
- Decoding happens lazily on first access
- In-Memory: Decoded
ByteArrayandStringvalues are cached perResourceinstance - Disk Cache: Large resources can be cached in the system temp directory
- Validation: Cached files are validated by hash to ensure integrity
Resources extracted to disk are automatically validated:
val filePath = resources.asPath("important.dat")
// File is validated by comparing hashes
// If validation fails, the file is regenerated- Use
write()for large files instead ofasBytes()to avoid loading into memory - Set appropriate
inMemoryCutoffbased on your application's memory constraints - Use
OptimizationStrategy.Memoryfor resources >4MB - Cache
Resourcesinstance - don't create multiple instances of the sameResourceDirectory
Ensure the generateKtEmbedResources task runs before compilation:
tasks.named("compileKotlin") {
dependsOn("generateKtEmbedResources")
}This is handled automatically by the plugin, but can be added explicitly if needed.
Reduce the inMemoryCutoff value:
val resources = Resources(ResourceDirectory(), inMemoryCutoff = 1_000_000) // 1MBOr use OptimizationStrategy.Memory for large resources:
resources.write("large-file.bin", output, OptimizationStrategy.Memory)The filter function returns true for paths to ignore:
// Correct: ignore .tmp files
filter = { path -> path.endsWith(".tmp") }
// Incorrect: this would ignore everything EXCEPT .tmp files
filter = { path -> !path.endsWith(".tmp") }