Skip to content

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.

On this page

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!")
utf-8 · kotlin · Lines: 2
Terminal
bash
elide kotlinc -- -d out Hello.kt
elide java -- -cp out HelloKt
Compiling and running Kotlin source with Elide
 ~/demo  elide kotlinc -- -d out Greeter.kt
✅  1 Kotlin sources compiled to `out` (301ms)
 ~/demo  elide java -- -cp out GreeterKt
Hello, Ada!
Hello, Linus!
 







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(" "))
utf-8 · kotlin · Lines: 1
CommandResult
elide kotlinc -- -d out Hello.kt then elide java -- -cp out HelloKtNoClassDefFoundError: kotlin/collections/CollectionsKt
elide kotlinc -- -include-runtime -d hello.jar Hello.ktCompilation fails
elide run Hello.ktHello Kotlin
elide build with a Jvm.JvmApplication artifactStart script with the standard library in lib/

Elide options

OptionPurpose
--incrementalKeep incremental state in .dev/artifacts/kotlinc/incremental/, keyed by output path.
--incremental-cache-dir DIRStore incremental state in DIR.
--plugins LISTEnable exactly these built-in plugins: metro, serialization, atomicfu, power-assert, compose.
--metro, --no-metroForce the Metro DI plugin on or off.
--compose, --no-composeForce the opt-in Compose compiler plugin on or off; requires a Compose runtime dependency when enabled.
--testsEnable the test plugins (power-assert).
--max-errors NErrors shown; 0 shows all. Warnings are de-duplicated and never limited.
--report-used-deps PATHWrite the classpath entries the compiler read, supertypes included (Bazel strict deps).
--jdeps PATHWrite a Bazel .jdeps proto: each classpath entry explicit, implicit or unused, with the classes used from it and their SHA-256 hashes.
--target-label LABELTarget label recorded in the --jdeps proto.
--persistent_workerRun as a Bazel persistent worker, singleplex or multiplex.
--abi-onlyWrite only an ABI jar to -d, skipping code generation for non-inline bodies.
--unused-inputs PATHWrite the classpath entries the compile neither used nor searched (Bazel unused_inputs_list).
--toolchain-home DIRRun the compiler from an absolute Kotlin distribution directory, instead of the embedded compiler.
--toolchain-classpath PATHSRun a managed compiler with its exact absolute jar classpath, separated by the platform path separator. Mutually exclusive with --toolchain-home.
--report-abi PATHAtomically publish the selected compiler’s complete opaque classpath snapshot after successful compilation. The path must be absolute.

With the bundled compiler, --incremental can be combined with --emit-abi to publish a complete header on every successful request, including requests with no implementation changes. Header generation performs a complete pass; retain the implementation output directory together with its incremental cache. Pass the complete source list on each request. A failed header compile leaves the previous header unchanged.

Selected-toolchain workers

Persistent-worker requests select their compiler before --, using either --toolchain-home or --toolchain-classpath. The worker keeps one isolated JVM per selection; it never adds Elide’s embedded compiler or plugin jars to that JVM. Compiler arguments and version-matched -Xplugin paths follow --. A missing or incompatible selected compiler fails the request rather than falling back to the embedded compiler.

Managed classpaths must include the compiler’s runtime dependencies; source/runtime dependencies still belong in the compiler’s -classpath. The worker uses the distribution’s JDK to source-launch its compiler adapter.

Selected Kotlin 2.2.21 workers use that compiler’s FIR incremental runner and classpath snapshots. --incremental requires a directory output, not a jar. In persistent incremental requests, --transactional-incremental preserves healthy compiler caches and class files after a source error when the compiler confirms rollback; --warm-incremental runs two additional source-change rounds to warm the incremental path. ABI reports are compiler-version-specific opaque bytes, not ABI jars, and failed compiles leave the previous report unchanged.

For kapt, submit plain requests without --incremental: first aptMode=stubs, then, if processing is needed, aptMode=apt. Keep the same handwritten Kotlin and Java snapshot arguments and private stubs directory for both requests. Kapt’s apt-only mode skips Kotlin analysis and collects the saved stubs itself; replacing the compiler arguments with the stub files drops handwritten Java inputs. Keep generated sources and classes directories private too, and compile generated sources separately after successful processing.

ABI jars

Terminal
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.

Terminal
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.

Built-in compiler plugins

Elide ships the plugins below, each pinned to the compiler that loads them. A project toggles them in the manifest; elide kotlinc takes --plugins LIST or the matching --<name> / --no-<name> flag.

PluginManifest switchDefault
kotlinx.serializationkotlin.features.serializationOn
AtomicFUkotlin.features.atomicfuOn
Power-Assertkotlin.features.powerAssertOn
Metro (DI)kotlin.features.metroOn for elide build, explicit for elide kotlinc
Composekotlin.features.composeOff; defaultPlugins does not turn it on

kotlin.features.defaultPlugins = false turns off the default suite.

Compose

Compose is opt-in because the Compose compiler fails any module whose classpath has no Compose runtime, whether or not the sources declare @Composable. Enable it alongside a Compose runtime dependency, which Elide does not supply:

pkl
kotlin {
  features {
    compose = true
  }
}
utf-8 · pkl · Lines: 5

elide kotlinc reads the same switch, so the two agree for one project; pass --compose (or --plugins=compose) to request it without a manifest, and --no-compose to force it off.

Plugin options pass through kotlin.plugins, keyed by the compiler plugin id:

pkl
kotlin {
  plugins {
    ["androidx.compose.compiler.plugins.kotlin"] {
      ["reportsDestination"] = "build/compose-reports"
    }
  }
}
utf-8 · pkl · Lines: 7

Like AGP’s debug variant, elide build adds sourceInformation=true for modules that are compiled for a debug Android application, so previews and inspectors can locate each composable call. A module is debug when it declares an application (Android.AndroidApk or Android.AndroidAab) with release = false. A module that declares no application is debug when a workspace project that depends on it, directly or through other members, declares one. Other modules keep the compiler default, so release APKs carry no source information. A sourceInformation entry in kotlin.plugins takes precedence, and elide kotlinc never adds the option.

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" }
  }
}
utf-8 · pkl · Lines: 9
PropertyMeaningDefault
kotlin.apiLevel, kotlin.languageLevel"2.2" … "2.5", "latest" or "stable"."auto"
compilerOptions.optInMarkers passed as -opt-in.none
compilerOptions.progressiveMode, .extraWarnings, .allWarningsAsErrors, .suppressWarnings, .verboseThe matching kotlinc switches.false
compilerOptions.freeCompilerArgsExtra compiler arguments.none
compilerOptions.apiVersion, .languageVersion, .jvmTargetPer-compile versions; "auto" follows the project."auto"
compilerOptions.javaParametersEmit parameter metadata for Java reflection.false
compilerOptions.jvmTargetValidationMode"ERROR", "WARNING" or "IGNORE" on Kotlin/Java target mismatch."ERROR"
compilerOptions.noStdlib, .noJdkDrop the stdlib or JDK from the classpath.false
compilerOptions.includeRuntimeUnsupported: true fails the Kotlin compile. Package with a Jvm.JvmApplication artifact.false
compilerOptions.incrementalIncremental project builds.true

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

Plugins and features

kotlin.featuresEffectDefault
kotlinxKotlinX libraries on Kotlin dependency roots.true
defaultPluginsBuilt-in plugin suite.true
serializationkotlinx.serialization plugin and runtime.kotlinx && defaultPlugins
coroutineskotlinx.coroutines runtime.kotlinx
atomicfukotlinx.atomicfu plugin and runtime.kotlinx && defaultPlugins
powerAssertPower Assert plugin.defaultPlugins
metroMetro DI plugin.defaultPlugins
composeOpt-in Compose compiler plugin; requires an explicit Compose runtime dependency.false
reflectionImplicit kotlin-reflect.true
testingKotlin 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" }
  }
}
utf-8 · pkl · Lines: 16
  • 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.
  • 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" }
}
utf-8 · pkl · Lines: 7
ToolchainBehavior
"embedded"Elide’s bundled Kotlin.
ManagedKotlinToolchainDownloads 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.
CustomKotlinToolchainA 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, except KAPT for Android projects with processors declared: 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

Terminal
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.

OptionPurpose
--kapt-sources PATHWrite generated sources to this srcjar.
--kapt-classes PATHWrite generated classes and resources to this jar.
--kapt-stubs PATHWrite the Kotlin header processors read to this jar.
--processorpath PATHSProcessor classpath.
--processors LISTProcessor classes to run; otherwise discovered on the processor path.
--processor-option K=VProcessor option, like javac -AK=V; repeatable.

Kotlin Symbol Processing

Terminal
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.

OptionPurpose
--ksp-output DIRWrite generated Kotlin to DIR/kotlin, Java to DIR/java, resources to DIR/resources, classes to DIR/classes.
--ksp-sources PATHWrite generated Kotlin and Java sources to this srcjar.
--ksp-classes PATHWrite generated classes and resources to this jar.
--processorpath PATHSProcessor classpath.
--processor-option K=VProcessor 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:

Terminal
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:

Terminal
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

Start from a template:

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

Was this page helpful?