# Kotlin compiler

> Compile Kotlin 2.5 with `elide kotlinc` or in an Elide project build, with built-in compiler plugins such as serialization and Metro, and ABI jars.

Elide bundles Kotlin 2.5.0-Beta2. A runtime-only distribution reports that `kotlinc` needs the full distribution.

## Compile source files

```kotlin
// Hello.kt
fun main() = println("Hello!")
```

```bash
elide kotlinc -- -d out Hello.kt
elide java -- -cp out HelloKt
```

<img src="https://elide.help/assets/demos/kotlinc.gif" alt="Compiling and running Kotlin source with Elide" width="1591" height="546" loading="lazy" decoding="async" class="rounded-lg border border-border my-6 w-full" />

`kotlinc` options go after `--`; Elide options go before it. `elide kotlinc --help` lists Elide's,
`elide kotlinc -- -help` lists `kotlinc`'s.

`elide java` does not add the Kotlin standard library, so code that calls it (`listOf`, `joinToString`) fails there:

```kotlin
fun main() = println(listOf("Hello", "Kotlin").joinToString(" "))
```

| Command | Result |
| --- | --- |
| `elide kotlinc -- -d out Hello.kt` then `elide java -- -cp out HelloKt` | `NoClassDefFoundError: kotlin/collections/CollectionsKt` |
| `elide kotlinc -- -include-runtime -d hello.jar Hello.kt` | Compilation fails |
| `elide run Hello.kt` | `Hello Kotlin` |
| `elide build` with a [`Jvm.JvmApplication`](https://elide.help/docs/toolchain/jvm/jar#runnable-application-distributions) artifact | Start script with the standard library in `lib/` |

## Elide options

| Option | Purpose |
| --- | --- |
| `--incremental` | Keep incremental state in `.dev/artifacts/kotlinc/incremental/`, keyed by output path. |
| `--incremental-cache-dir DIR` | Store incremental state in `DIR`. |
| `--plugins LIST` | Enable exactly these built-in plugins: `metro`, `serialization`, `atomicfu`, `power-assert`. |
| `--metro`, `--no-metro` | Force the Metro DI plugin on or off. |
| `--tests` | Enable the test plugins (power-assert). |
| `--max-errors N` | Errors shown; `0` shows all. Warnings are de-duplicated and never limited. |
| `--report-used-deps PATH` | Write the classpath entries the compiler read, supertypes included (Bazel strict deps). |
| `--jdeps PATH` | Write a Bazel `.jdeps` proto: each classpath entry explicit, implicit or unused, with the classes used from it and their SHA-256 hashes. |
| `--target-label LABEL` | Target label recorded in the `--jdeps` proto. |
| `--persistent_worker` | Run as a Bazel persistent worker, singleplex or multiplex. |
| `--abi-only` | Write only an ABI jar to `-d`, skipping code generation for non-inline bodies. |
| `--unused-inputs PATH` | Write the classpath entries the compile neither used nor searched (Bazel `unused_inputs_list`). |

### ABI jars

```bash
elide kotlinc --abi-only -- -d header.jar src/**/*.kt
elide kotlinc -- -d app.jar --emit-abi header.jar src/**/*.kt
```

The jar holds public and protected signatures, `@Metadata`, ABI-relevant `inline` bodies, `const` values and
default-argument markers, including declarations that compiler plugins generate, such as kotlinx.serialization's
`$serializer`. The ABI output is a compile-time input; run programs against the full implementation classes and
runtime dependencies.

`--abi-only` writes only the ABI jar. Body stripping is enabled by default: the compiler still analyzes the sources,
but skips most lowering and code generation for non-inline bodies. It retains bodies needed by consumers, including
`inline` functions. This mode does not replace a full compile when validating a release.

`--emit-abi PATH`, after `--`, writes a header alongside a full compile. Either output can be a `.jar` or a directory.
The header-only mode avoids implementation-generated synthetic members that can appear in a header from a full compile,
so the two modes need not produce identical bytes for the same sources. Body-only edits leave the stripped header
unchanged; edits to `inline` bodies, constants or signatures can change it.

```bash
elide kotlinc --abi-only -- -jvm-target 21 -d lib-abi.jar Library.kt
elide kotlinc -- -jvm-target 21 -cp lib-abi.jar -d consumer.jar Consumer.kt
elide kotlinc -- -jvm-target 21 -d lib.jar Library.kt
```

Use `lib.jar` at runtime, along with the Kotlin standard library and other runtime dependencies. Passing the header
alone to `elide java` does not provide an implementation.

For mixed Kotlin/Java inputs, both ABI modes include Java declarations in the header. The Java header resolves against
the Kotlin header and the supplied classpath; `-jvm-target` sets its class-file target. A normal full Kotlin compile
reads `.java` files for resolution but does not emit their implementation classes: compile those separately with
`elide javac`. Java annotation processors are not run by this mixed-source header step.

`ELIDE_KOTLINC_ABI_STRIP=0` disables body stripping for `--abi-only`, running full code generation before keeping the
header. This is a diagnostic fallback and does not turn the output into a runnable implementation. Workers accept the
setting per request.

## Build projects

`elide build` compiles Kotlin source sets found by convention or declared in `elide.pkl`.

```pkl
kotlin {
  apiLevel = "auto"
  languageLevel = "auto"
  compilerOptions {
    allWarningsAsErrors = true
    optIn { "kotlin.time.ExperimentalTime" }
    freeCompilerArgs { "-Xcontext-parameters" }
  }
}
```

| Property | Meaning | Default |
| --- | --- | --- |
| `kotlin.apiLevel`, `kotlin.languageLevel` | `"2.2"` … `"2.5"`, `"latest"` or `"stable"`. | `"auto"` |
| `compilerOptions.optIn` | Markers passed as `-opt-in`. | none |
| `compilerOptions.progressiveMode`, `.extraWarnings`, `.allWarningsAsErrors`, `.suppressWarnings`, `.verbose` | The matching `kotlinc` switches. | `false` |
| `compilerOptions.freeCompilerArgs` | Extra compiler arguments. | none |
| `compilerOptions.apiVersion`, `.languageVersion`, `.jvmTarget` | Per-compile versions; `"auto"` follows the project. | `"auto"` |
| `compilerOptions.javaParameters` | Emit parameter metadata for Java reflection. | `false` |
| `compilerOptions.jvmTargetValidationMode` | `"ERROR"`, `"WARNING"` or `"IGNORE"` on Kotlin/Java target mismatch. | `"ERROR"` |
| `compilerOptions.noStdlib`, `.noJdk` | Drop the stdlib or JDK from the classpath. | `false` |
| `compilerOptions.includeRuntime` | Unsupported: `true` fails the Kotlin compile. Package with a `Jvm.JvmApplication` artifact. | `false` |
| `compilerOptions.incremental` | Incremental project builds. | `true` |

Each compile writes its arguments to `kotlinc.args` beside its output.

### Plugins and features

| `kotlin.features` | Effect | Default |
| --- | --- | --- |
| `kotlinx` | KotlinX libraries on Kotlin dependency roots. | `true` |
| `defaultPlugins` | Built-in plugin suite. | `true` |
| `serialization` | kotlinx.serialization plugin and runtime. | `kotlinx && defaultPlugins` |
| `coroutines` | kotlinx.coroutines runtime. | `kotlinx` |
| `atomicfu` | kotlinx.atomicfu plugin and runtime. | `kotlinx && defaultPlugins` |
| `powerAssert` | Power Assert plugin. | `defaultPlugins` |
| `metro` | Metro DI plugin. | `defaultPlugins` |
| `reflection` | Implicit `kotlin-reflect`. | `true` |
| `testing` | Kotlin test libraries on test sets. | `true` |

```pkl
kotlin {
  features {
    metro = false
  }
  plugins {
    ["org.jetbrains.kotlinx.serialization"] {
      ["mode"] = "json"
    }
  }
}

dependencies {
  maven {
    kotlinPlugins { "com.example:my-compiler-plugin:1.0.0" }
  }
}
```

- Each `plugins` entry becomes `-P plugin:<id>:<key>=<value>`, sorted by id and key.
- Third-party plugins go in the `kotlinPlugins` bucket and need a `ManagedKotlinToolchain` or
  `CustomKotlinToolchain`. The embedded compiler warns `Custom Kotlin compiler plugins are not supported when using the
  embedded compiler, plugins will not be applied` and compiles without them.
- A built-in plugin missing from the distribution is skipped, with its options, and warns.
- Implicit Kotlin libraries go only to dependency roots with Kotlin sources. `reflection = false` drops
  `kotlin-reflect`; `compilerOptions.noStdlib = true` drops it and the stdlib. Declared dependencies stay.
- `project("core").friend()` exposes a sibling's `internal` declarations; see
  [Workspaces](https://elide.help/docs/elide-pkl-reference#workspaces).
- A source set sees the `internal` declarations of the sets it depends on in the same project: every set named in its
  `dependsOn` (`main` for a test set that leaves it out), along with the sets those depend on.

### Choose a toolchain

```pkl
import "elide:Kotlin.pkl" as Kotlin

kotlin {
  toolchain = "embedded"                                       // the default
  // toolchain = new Kotlin.ManagedKotlinToolchain { version = "2.4.0" }
  // toolchain = new Kotlin.CustomKotlinToolchain { path = "/opt/kotlinc" }
}
```

| Toolchain | Behavior |
| --- | --- |
| `"embedded"` | Elide's bundled Kotlin. |
| `ManagedKotlinToolchain` | Downloads that version (prereleases too) with its stdlib, reflect, test libraries and Kotlin plugins. KotlinX, Metro and other third-party versions are unchanged; check they match. |
| `CustomKotlinToolchain` | A local Kotlin distribution; built-in plugins it lacks are skipped. |

The toolchain applies to project builds; `elide kotlinc` always uses the bundled compiler.

Project builds don't run annotation processors or KSP over Kotlin sources: Kotlin code that uses generated types must
get them from an upstream source set or project. `elide kotlinc --kapt-sources` and `--ksp-output` (below) do this for
command-line and Bazel builds.

## Annotation processors over Kotlin

```bash
elide kotlinc --kapt-sources gen.srcjar --kapt-classes gen.jar --processorpath dagger-compiler.jar -- \
  -cp deps.jar src/**/*.kt src/**/*.java
```

`--kapt-sources` runs Java annotation processors instead of compiling, so Kotlin code can use what they generate:
compile again with the generated sources added. Processors see the Kotlin declarations as classes from an ABI header,
with no stub generation, and the Java sources as sources. References to classes processors have yet to generate don't
stop processing; where one appears in a signature, processors see `Any?`, where KAPT shows `error.NonExistentClass`.

| Option | Purpose |
| --- | --- |
| `--kapt-sources PATH` | Write generated sources to this srcjar. |
| `--kapt-classes PATH` | Write generated classes and resources to this jar. |
| `--kapt-stubs PATH` | Write the Kotlin header processors read to this jar. |
| `--processorpath PATHS` | Processor classpath. |
| `--processors LIST` | Processor classes to run; otherwise discovered on the processor path. |
| `--processor-option K=V` | Processor option, like `javac -AK=V`; repeatable. |

## Kotlin Symbol Processing

```bash
elide kotlinc --ksp-output gen --processorpath moshi-kotlin-codegen.jar:moshi.jar -- -cp moshi.jar src/**/*.kt
```

`--ksp-output` (or either jar option) runs KSP2 symbol processors instead of compiling, then compile again with the
generated sources added.
KSP2 runs in the bundled toolchain and uses its Kotlin version. Supply KSP2 processors on `--processorpath` with each
processor's dependencies; Elide supplies the Kotlin standard library and KSP runtime. Providers are discovered through
`META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider` in those JARs. `--processors` selects
Java annotation processors for the separate `--kapt-sources` mode; it does not select KSP providers.

| Option | Purpose |
| --- | --- |
| `--ksp-output DIR` | Write generated Kotlin to `DIR/kotlin`, Java to `DIR/java`, resources to `DIR/resources`, classes to `DIR/classes`. |
| `--ksp-sources PATH` | Write generated Kotlin and Java sources to this srcjar. |
| `--ksp-classes PATH` | Write generated classes and resources to this jar. |
| `--processorpath PATHS` | Processor classpath. |
| `--processor-option K=V` | Processor option; repeatable. |

KSP options go before `--`; compiler source, classpath and target options go after it. The following example assumes a
processor JAR that generates `example.AppGenerated` from `src/App.kt`:

```bash
elide kotlinc --ksp-output gen --processorpath processor.jar:processor-deps.jar \
  --processor-option mode=production -- \
  -jvm-target 21 -cp annotations.jar src/App.kt
elide kotlinc -- -jvm-target 21 -cp annotations.jar -d app.jar \
  src/App.kt gen/kotlin/example/AppGenerated.kt
```

The first command only processes symbols; `-d` does not request an application compile in that mode. Add every generated
source needed by the second command. If the processor generates Java, pass those sources for Kotlin resolution and
compile them separately with `elide javac`, using the Kotlin output on its classpath. Copy generated resources into the
application's packaged resources; they are not compiled Kotlin sources.

Use archive outputs when a build system needs explicit processing artifacts:

```bash
elide kotlinc --ksp-sources gen.srcjar --ksp-classes gen.jar \
  --processorpath processor.jar:processor-deps.jar -- -cp annotations.jar src/App.kt
```

Either archive option activates KSP even without `--ksp-output`. The sources archive contains generated Kotlin and Java;
the classes archive contains classes and resources emitted by processors, not compiled application classes. Supplying
`--ksp-output` as well keeps the directory outputs too. KSP2 processing, Java annotation processing and Kotlin compiler
plugins are separate phases; project `kotlin.plugins` configuration does not schedule KSP. Project builds do not yet
run this processing phase automatically.

## Samples

- [Kotlin collections](https://elide.help/docs/code-samples#kotlin-collections)
- [Kotlin tests](https://elide.help/docs/code-samples#kotlin-tests)

Start from a template:

```bash
elide init --template ktjvm       # Kotlin app with a test
```
