Compile-time KSP generation
Kotlin Symbol Processing (KSP) generates loader classes at build time. No reflection, no magic — inspect generated code in build/generated/ksp.
Kotlin · KSP · Environment Configuration
Stop hand-rolling System.getenv() calls, string parsing, and default-value logic.
Envy loads environment variables into ordinary Kotlin classes — with compile-time code generation and zero reflection at runtime.
A few environment variables are easy enough:
val databaseUrl = System.getenv("databaseUrl")
val port = System.getenv("port")?.toInt()
But as configuration grows, this approach becomes repetitive. You end up converting strings, handling missing values, defining defaults, validating configuration, and wiring everything together yourself.
Envy gives Kotlin applications a simpler path: declare a configuration class, annotate it, and load it in one line.
A small, focused library for JVM and Kotlin backends that need reliable, type-safe access to environment variables.
Kotlin Symbol Processing (KSP) generates loader classes at build time. No reflection, no magic — inspect generated code in build/generated/ksp.
Supports String, Int, Long, Double, Float, Boolean, Byte, Short, and Char with compile-time parsing.
Use @EnviedDefault for fallback values when an environment variable is unset. Defaults are always explicit — no hidden constructor defaults.
Optional configuration? Declare nullable types. Envy resolves unset variables to null when no default is provided.
Use @EnviedName when deployment variables follow a different naming convention than your Kotlin properties (e.g. DATABASE_URL mapped to url).
Envy.load() can be called anywhere in your application. Repeated calls return the same cached configuration instance.
Generated loaders register via Java ServiceLoader. Runtime discovery is transparent and reflection-free.
Add Envy to your Gradle project and define a configuration class in three steps.
Enable KSP and add the runtime library plus the compile-time processor.
// build.gradle.kts
plugins {
id("com.google.devtools.ksp") version "2.3.11"
}
dependencies {
implementation("io.github.allopensource:envy:0.2.0")
ksp("io.github.allopensource:envy-ksp:0.2.0")
}
Annotate a class with @Envied. Property names map directly to environment variable names.
@Envied
class AppConfig(
val databaseUrl: String,
val port: Int,
@EnviedDefault("false")
val debug: Boolean,
val apiKey: String?,
)
One call. Type-safe. Cached.
import io.github.allopensource.envy.Envy
val config = Envy.load<AppConfig>()
// databaseUrl from env, port from env, debug from @EnviedDefault, apiKey nullable
By default, property names are used as environment variable names. Use @EnviedName to map a property to a different variable:
@Envied
class DatabaseConfig(
@EnviedName("DATABASE_URL")
val url: String,
@EnviedName("DATABASE_POOL_SIZE")
val poolSize: Int,
)
DATABASE_URL=postgres://localhost/mydb
DATABASE_POOL_SIZE=20
For each property, Envy resolves values in this order:
System.getenv(propertyName) when set, or the name from @EnviedName when present
Non-null properties without an environment variable or @EnviedDefault throw
EnvyConfigurationException at runtime. Invalid values (e.g. a non-numeric string for Int) also throw.
KSP scans classes annotated with @Envied and generates an EnvyLoader implementation per class.
The loader reads environment variables and constructs your configuration object. Entries are added to
META-INF/services/io.github.allopensource.envy.EnvyLoader.
// Generated by envy-ksp
class EnvyLoaderForAppConfig : EnvyLoader<AppConfig> {
override val type = AppConfig::class
override fun load(): AppConfig {
return AppConfig(
databaseUrl = System.getenv("databaseUrl")!!,
port = System.getenv("port")!!.toInt(),
debug = false,
apiKey = null,
)
}
}
Envy discovers loaders via Java's ServiceLoader on startup.
When you call Envy.load<AppConfig>(), the matching loader is used to construct the instance.
The result is cached so repeated loads are free.
The runtime (envy) and compile-time (envy-ksp) components are deliberately separated —
your application only needs the processor at build time.
envy — runtime API (@Envied, @EnviedDefault, @EnviedName, Envy, EnvyLoader)envy-ksp — KSP processor that generates loadersenvy-tests — integration testsEnvy is actively evolving. Planned improvements include:
Read the launch post: Revisiting environment configuration for Kotlin
Star the repo, open an issue, or share how you're using type-safe environment configuration.