Kotlin · KSP · Environment Configuration

Type-safe, reflection-free environment configuration for Kotlin

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.

MIT License JDK 17+ v0.2.0

Envy — Kotlin environment configuration library

Configuration looks simple — until your app grows

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.

Why use Envy for Kotlin environment configuration?

A small, focused library for JVM and Kotlin backends that need reliable, type-safe access to environment variables.

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.

Type-safe primitives

Supports String, Int, Long, Double, Float, Boolean, Byte, Short, and Char with compile-time parsing.

Explicit defaults

Use @EnviedDefault for fallback values when an environment variable is unset. Defaults are always explicit — no hidden constructor defaults.

Nullable properties

Optional configuration? Declare nullable types. Envy resolves unset variables to null when no default is provided.

Custom env var names

Use @EnviedName when deployment variables follow a different naming convention than your Kotlin properties (e.g. DATABASE_URL mapped to url).

Singleton caching

Envy.load() can be called anywhere in your application. Repeated calls return the same cached configuration instance.

ServiceLoader discovery

Generated loaders register via Java ServiceLoader. Runtime discovery is transparent and reflection-free.

Quick start: load environment variables in Kotlin

Add Envy to your Gradle project and define a configuration class in three steps.

1

Add dependencies

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")
}
2

Declare your configuration class

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?,
)
3

Load at runtime

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

Custom environment variable names

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

Resolution order

For each property, Envy resolves values in this order:

  1. Environment variableSystem.getenv(propertyName) when set, or the name from @EnviedName when present
  2. @EnviedDefault — compile-time default when the variable is unset
  3. null — for nullable properties with no env var and no default

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.

How Envy works: KSP loaders and ServiceLoader

Compile time — KSP generates loaders

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,
        )
    }
}

Runtime — reflection-free discovery

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 loaders
  • envy-tests — integration tests

What's next for Envy

Envy is actively evolving. Planned improvements include:

  • Support for more types and complex properties
  • Better configuration validation
  • Nested configuration classes

Read the launch post: Revisiting environment configuration for Kotlin

Try Envy in your Kotlin project

Star the repo, open an issue, or share how you're using type-safe environment configuration.