skip to content

Compilation Targets

The backends the compiler can emit: JVM, Native for each platform triple, JS, Wasm, plus the Android and iOS packaging targets. Which targets a project declares determines everything else about its structure.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

explore

questions

25

In a Kotlin Multiplatform module, what artifact does the androidTarget() produce versus the iosArm64()/iosSimulatorArm64() targets, and how does each get consumed?

level: juniorimportance: must knowfreq 62%

answer

  1. androidTarget() -> AAR over Android SDK
  2. iosArm64 = device, iosSimulatorArm64 = Apple-silicon simulator
  3. iOS targets are Kotlin/Native -> .framework
  4. commonMain shared, androidMain/iosMain actuals
  5. android() renamed androidTarget() in 1.9

basics

~10 s

androidTarget() builds an Android library (an AAR) that Android apps use. The iOS targets build a native framework that an Xcode/Swift app imports. Same Kotlin code, two different output packages.

solid answer

~30 s

In the kotlin { } block, androidTarget() configures compilation against the Android SDK and produces an Android Archive (AAR) consumed via Gradle by an Android app module. iosArm64() (real devices) and iosSimulatorArm64() (Apple-silicon simulator) are Kotlin/Native targets that compile to a .framework (or XCFramework) which Xcode/Swift imports. The shared module declares these targets; commonMain holds shared code, androidMain and iosMain hold platform-specific actual implementations of expect declarations. Android consumes the AAR like any Gradle dependency; iOS links the framework. The same source set feeds both, so business logic is written once and packaged twice for the two platforms.

code

kotlin · 11 lines
kotlin
kotlin {
    androidTarget()          // -> AAR consumed by Android app
    iosArm64()               // -> device framework
    iosSimulatorArm64()      // -> Apple-silicon simulator framework

    sourceSets {
        commonMain.dependencies { /* shared deps */ }
        androidMain.dependencies { /* Android deps */ }
        iosMain.dependencies { /* iOS deps */ }
    }
}

go deeper

for a junior

Knows androidTarget() makes an AAR and iOS targets make a framework, and that code is shared via commonMain.

for a middle

Distinguishes device vs simulator targets and explains expect/actual feeding both artifacts.

for a senior

Explains JVM-backend vs Kotlin/Native-backend differences and consumption paths (Gradle dependency vs Xcode linking).

for a principal

Frames target selection as a packaging/distribution decision and can reason about XCFramework vs single framework trade-offs.

## What a KMP target is A **target** in Kotlin Multiplatform (KMP) is a single platform you compile your shared code for. You declare targets inside the `kotlin { }` DSL of a Gradle build file. Each target pulls in a platform-specific compiler backend and produces a platform-native artifact. ## androidTarget() - `androidTarget()` compiles your Kotlin against the **Android SDK** using the JVM backend (Android runs Kotlin/JVM bytecode, later turned into DEX by the Android toolchain). - It requires the **Android Gradle Plugin (AGP)** to be applied (`com.android.library`). - The output is an **AAR (Android Archive)** - a zip containing compiled classes plus Android resources/manifest - which an Android app module consumes as a normal Gradle dependency. - Renamed from the old `android()` to `androidTarget()` in Kotlin 1.9 to free up `android` for a future Android-specific DSL. ## iosArm64() and iosSimulatorArm64() These are **Kotlin/Native** targets - they compile straight to machine code via LLVM, no JVM involved. - `iosArm64()` -> ARM64 binary for **real iPhones/iPads**. - `iosSimulatorArm64()` -> ARM64 binary for the **iOS Simulator running on Apple-silicon Macs**. - (`iosX64()` exists for Intel-Mac simulators.) - Output is an Apple **`.framework`** (or several combined into an **XCFramework**) that an Xcode project imports and Swift/Objective-C code calls. ## Source sets tie them together ```kotlin kotlin { androidTarget() iosArm64() iosSimulatorArm64() sourceSets { commonMain.dependencies { /* shared */ } androidMain.dependencies { /* Android-only */ } iosMain.dependencies { /* iOS-only */ } } } ``` `commonMain` is the shared code. `expect` declarations there get platform `actual` implementations in `androidMain` / `iosMain`. The compiler produces the AAR and the framework from the same common source plus the platform source set. ## How each is consumed - **Android:** add the shared module as a Gradle dependency; Gradle resolves the AAR. - **iOS:** link the framework into the Xcode app (manually, or via CocoaPods/SwiftPM packaging).

  • Why was android() renamed to androidTarget()?
    To reserve the cleaner android name for a future Android-specific DSL; androidTarget() is the current spelling since Kotlin 1.9.
  • Do iOS targets use the JVM?
    No. They are Kotlin/Native targets compiled to native machine code via LLVM; there is no JVM or JVM-style runtime.

One recipe (commonMain), two kitchens: the Android kitchen plates an AAR, the iOS kitchen plates a framework.

saying these in an interview costs you the question

  • Says iOS targets produce a JAR or run on a JVM
  • Thinks androidTarget() outputs an APK rather than an AAR library
  • Confuses iosArm64 (device) with the simulator target
  • Believes you must duplicate business logic per platform

context

open as a page

What is the Kotlin/JS IR target, and how do you enable it in a Gradle build with browser() and nodejs() environments?

level: juniorimportance: must knowfreq 55%

basics

~10 s

Kotlin/JS lets you compile Kotlin code into JavaScript. The IR target is the modern compiler backend. In Gradle you add js(IR) and pick browser() or nodejs() depending on where the code runs.

open as a page

In a Kotlin Multiplatform build, what does declaring the jvm() target do, and what kind of artifacts does it produce?

level: juniorimportance: must knowfreq 60%

basics

~10 s

Adding jvm() tells the compiler to build a JVM version of your code. It produces Java bytecode (.class files, usually packaged in a .jar) that runs on any Java Virtual Machine.

open as a page

What is the Kotlin/Native target, and how does running a Kotlin/Native binary differ from running Kotlin on the JVM?

level: juniorimportance: must knowfreq 60%

basics

~10 s

Kotlin/Native compiles Kotlin straight to a native machine-code program. There is no Java Virtual Machine running it; you get a standalone executable or library that runs directly on the operating system.

open as a page

What are the wasmJs() and wasmWasi() targets in a Kotlin Multiplatform project, and how do they differ?

level: juniorimportance: must knowfreq 55%

basics

~10 s

They are two Kotlin Multiplatform targets that compile code to WebAssembly. wasmJs() runs in the browser and can talk to JavaScript; wasmWasi() runs in standalone runtimes outside the browser using the WASI system interface.

open as a page

What is the difference between jvmToolchain(...) and compilerOptions.jvmTarget, and how do they interact?

level: middleimportance: must knowfreq 55%

basics

~10 s

jvmToolchain picks which JDK version actually compiles and runs your code. jvmTarget sets the bytecode version the compiler emits. The toolchain is the tool; jvmTarget is the output format.

open as a page

How does JavaScript interop in Kotlin/Wasm (wasmJs) differ from Kotlin/JS, and what are the rules for external declarations?

level: middleimportance: must knowfreq 45%

basics

~20 s

Both let Kotlin call JavaScript, but Kotlin/Wasm runs as a real Wasm module, so values crossing the boundary are limited and use a special JsAny type family. Kotlin/JS compiles to JavaScript directly, so its interop is looser and supports dynamic.

open as a page

What is the @ExperimentalWasmDsl marker and when do you need to opt in to it?

level: juniorimportance: should knowfreq 40%

basics

~20 s

It is an annotation that marks the Wasm parts of the Gradle build DSL as experimental. You add an opt-in (like @OptIn) when calling wasmJs() or wasmWasi(), acknowledging that this configuration API may still change.

open as a page

Compare the two main ways to package a KMP framework for iOS consumers: the CocoaPods Gradle plugin versus the Swift Package Manager (SwiftPM) integration. What does each generate and when would you pick one?

level: middleimportance: should knowfreq 40%

basics

~20 s

The CocoaPods plugin makes your shared module a Pod the iOS app pulls via a Podfile. The SwiftPM route publishes the framework as an XCFramework referenced by a Package.swift. Pick CocoaPods if the app already uses Pods; SwiftPM for a Pod-free, native Apple toolchain.

open as a page

How do you configure the iOS framework output from a KMP module using the framework DSL inside binaries.framework {} - name, static vs dynamic, baseName, and exporting dependencies?

level: middleimportance: should knowfreq 48%

basics

~20 s

Each iOS target has a binaries.framework {} block where you set the framework name, choose static or dynamic linking, and export other KMP libraries so their public API also shows up in the Swift-facing framework.

open as a page

How does Dead Code Elimination work in the Kotlin/JS IR backend, and how does it affect bundle size of kotlin-stdlib-js?

level: middleimportance: should knowfreq 45%

basics

~10 s

Dead Code Elimination removes Kotlin code your app never uses from the final JavaScript. That keeps the bundle small, so you don't ship the whole standard library when you only call a few functions.

open as a page

Explain the source-set layout that the jvm() target adds to a KMP module and where JVM-only code that uses java.* APIs must live.

level: middleimportance: should knowfreq 45%

basics

~10 s

jvm() adds jvmMain and jvmTest source sets that build on top of commonMain/commonTest. Any code touching java.* APIs must go in jvmMain because common code has to compile for every target.

open as a page

Describe the Kotlin/Native memory model and garbage collector. What changed from the original (legacy) model, and what does it mean for sharing objects across threads?

level: middleimportance: should knowfreq 40%

basics

~10 s

Modern Kotlin/Native uses a tracing garbage collector and lets you share regular objects freely across threads, like the JVM. The old model that froze objects and forbade sharing is gone.

open as a page

How do you choose what a Kotlin/Native target produces — an executable, a shared library, or an Apple framework — using the Gradle binaries DSL?

level: middleimportance: should knowfreq 45%

basics

~10 s

Inside the target's binaries {} block you call executable(), sharedLib(), staticLib(), or framework(). Each tells the compiler what kind of output to build for that platform.

open as a page

A teammate ships a single iosArm64 .framework and the app fails to run on the Apple-silicon simulator. Explain the architecture problem and how XCFramework plus the right targets fix it.

level: seniorimportance: should knowfreq 38%

basics

~20 s

iosArm64 is for real devices only. The simulator on an Apple-silicon Mac needs iosSimulatorArm64. A plain .framework holds one slice, so the simulator can't link it. Build both targets and bundle them in an XCFramework, which carries multiple slices.

open as a page

How do you make the Kotlin/JS IR target emit ES modules, and what does that change about the output and interop?

level: seniorimportance: should knowfreq 38%

basics

~10 s

You tell the compiler to output ES modules instead of the default UMD format. Then the generated JavaScript uses import/export statements, so modern browsers and tools can load and tree-shake it natively.

open as a page

Explain @JsExport and how Kotlin types are exposed to JavaScript/TypeScript from the IR backend. What are the limits?

level: seniorimportance: should knowfreq 32%

basics

~10 s

@JsExport marks Kotlin declarations so they keep readable names and become callable from JavaScript. The IR backend can also generate TypeScript type files for them, but only certain Kotlin types map cleanly.

open as a page

Your KMP service must run on a production JVM 17 fleet but you want to build with the latest JDK. How do you configure the jvm() target, and what risks arise from getting the bytecode level wrong?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Pin jvmTarget to JVM_17 so the bytecode loads on JVM 17, while using a newer JDK toolchain to compile. If bytecode is too new, production throws UnsupportedClassVersionError at load time.

open as a page

How does the Kotlin/JVM target represent Kotlin features in bytecode so that Java callers can use them, and what annotations control this?

level: seniorimportance: should knowfreq 40%

basics

~10 s

Kotlin compiles to ordinary JVM bytecode, so Java can call it. Annotations like @JvmStatic, @JvmName, @JvmOverloads, and @JvmField adjust how Kotlin constructs appear to Java callers.

open as a page

How does Kotlin/Native interoperate with C libraries? Walk through cinterop, .def files, and how C types map into Kotlin.

level: seniorimportance: should knowfreq 35%

basics

~20 s

Kotlin/Native ships a tool called cinterop. You give it a small .def file pointing at a C header and library, and it generates a Kotlin package whose functions and types call straight into the C code.

open as a page

You're adding a Kotlin/Wasm browser frontend to an existing KMP library. How do you structure source sets and decide between wasmJs, Kotlin/JS, and Kotlin/Native for the web?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Put shared logic in commonMain and Wasm-specific JS interop code in wasmJsMain. Choose wasmJs for a Wasm browser app with JS interop, Kotlin/JS if you need broad runtime support today, and Kotlin/Native is not a web-frontend option.

open as a page

Why does Kotlin/Wasm depend on the WasmGC and exception-handling proposals, and what are the practical consequences?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Kotlin objects need garbage collection and Kotlin code throws exceptions. Instead of bundling those features, Kotlin/Wasm uses new WebAssembly proposals so the runtime provides GC and exception handling. The cost is that only recent runtimes can run the output.

open as a page

When wiring androidTarget() in a KMP module, what AGP setup and source-set/configuration details matter for producing a usable AAR (compileSdk, namespace, publishLibraryVariants, dependency configurations, manifest)?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

You apply the Android library Gradle plugin, set compileSdk/minSdk and a namespace in the android {} block, and put Android-only code in androidMain. androidTarget() then builds an AAR that an Android app can depend on. You can pick which build variants get published.

open as a page

Your team is migrating a Kotlin/JS app from the legacy backend to IR and bundle size unexpectedly grew. How do you reason about and resolve this?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

Check that you measured a production build, look for broad @JsExport that blocks code removal, confirm the right module format, and verify your JS interop and npm dependencies still tree-shake. The IR backend usually shrinks bundles, so growth signals a misconfiguration.

open as a page

You ship a Kotlin/Native CLI and an iOS framework. What toolchain and build-performance characteristics of Kotlin/Native should drive your engineering decisions, and how do you mitigate the rough edges?

level: principalimportance: nice to knowfreq 22%

basics

~10 s

Kotlin/Native compiles ahead-of-time with LLVM, which is slow and per-platform. Plan for long release builds, keep debug builds fast, cache aggressively, and don't expect the huge JVM library ecosystem.

open as a page