Why can KSP work in Kotlin Multiplatform projects while kapt cannot, and what does that imply for cross-platform code generation?
answer
- kapt needs javac => JVM only
- KSP = Kotlin compiler plugin => all targets
- KMP source sets: commonMain, iosMain…
- kspAndroid / kspIosArm64 configurations
- Room KMP, kotlin-inject use KSP
basics
~10 skapt needs a Java compiler, which only exists on the JVM, so it can't run for iOS or JavaScript targets. KSP is a pure Kotlin compiler plugin, so it runs on every Kotlin platform.
solid answer
~40 skapt is fundamentally tied to **javac**: it generates Java stubs and runs `javax.annotation.processing` inside the Java compiler. That pipeline only exists on the **JVM/Android** target, so kapt simply cannot operate for Native (iOS), JS, or Wasm targets in **Kotlin Multiplatform (KMP)**. KSP, by contrast, is a **Kotlin compiler plugin** built on the Kotlin symbol model with no javac dependency, so it can run for any Kotlin target the compiler supports. Practically, in a KMP build you apply KSP per target/source-set (e.g. `add("kspCommonMainMetadata", ...)`, `add("kspAndroid", ...)`, `add("kspIosArm64", ...)`), and generated code lands in the right source set. This is a major reason new multiplatform-aware libraries (e.g. Room's KMP support) standardize on KSP. kapt remains JVM-only and is effectively a dead end for non-JVM targets.
go deeper
Knows KSP works in multiplatform projects and kapt does not, even if hazy on why.
Pins the reason on kapt's javac dependency versus KSP being a pure Kotlin compiler plugin, and knows per-target KSP configs exist.
Discusses commonMain vs per-target generation, source-set placement, and library ecosystem implications (Room KMP).
Weighs migration strategy for a JVM codebase going multiplatform, including how kapt-only deps become blockers and how to sequence the move.
## Kotlin Multiplatform (KMP) in one paragraph **Kotlin Multiplatform** lets one codebase target multiple platforms: **JVM/Android**, **Kotlin/Native** (iOS, macOS, Linux), **Kotlin/JS**, and **Wasm**. You organize code into **source sets** — `commonMain` for shared code plus platform-specific sets like `androidMain` or `iosMain`. Each non-JVM target is compiled by a Kotlin backend that **does not involve javac at all**. ## Why kapt is JVM-only `kapt` works by: 1. Generating **Java stubs** from Kotlin. 2. Running **`javax.annotation.processing`** processors inside **javac**. Both steps require the **Java compiler**, which exists only for the **JVM/Android** target. For Kotlin/Native or Kotlin/JS there is no javac, no Java APT pipeline, and no Java stubs to feed it. Therefore kapt structurally **cannot** run for those targets — it is not a missing feature, it's an architectural impossibility. ## Why KSP is multiplatform-capable `KSP` is a **Kotlin compiler plugin**. It reads the Kotlin compiler's **symbol model** (`Resolver`, `KSClassDeclaration`, …) and emits source via a `CodeGenerator`. Nothing in that path needs javac, so KSP runs wherever the Kotlin compiler runs — every KMP target. ## How you wire KSP per target in KMP In a KMP Gradle build you add the processor to **per-source-set/per-target configurations** rather than a single `kapt` configuration: ```kotlin plugins { kotlin("multiplatform") id("com.google.devtools.ksp") } dependencies { add("kspCommonMainMetadata", "com.example:my-processor:1.0") add("kspAndroid", "com.example:my-processor:1.0") add("kspIosArm64", "com.example:my-processor:1.0") add("kspIosSimulatorArm64", "com.example:my-processor:1.0") } ``` Generated files are placed in the corresponding source set's build output and picked up by that target's compilation. ## Practical implications - **Library choice:** KMP-aware libraries (e.g. Room's multiplatform support, kotlin-inject, some serialization tooling) ship KSP processors precisely so they can generate code for non-JVM targets. - **commonMain generation:** Generating into `commonMain` (via `kspCommonMainMetadata`) lets one processor feed all targets, but requires care that emitted code uses only common APIs. - **kapt = JVM dead end:** If your project must go multiplatform, any kapt-only dependency becomes a blocker for non-JVM targets. ## Summary kapt's javac dependency confines it to the JVM; KSP's pure-Kotlin compiler-plugin design frees it to run on every Kotlin target, which is why multiplatform code generation standardizes on KSP.
- How do you point a KSP processor at a specific KMP target?Add it to that target's KSP configuration, e.g. add("kspIosArm64", "…processor…"), rather than one global config; generated code lands in that target's source set.
- What is the risk of generating into kspCommonMainMetadata?Generated code must use only common (multiplatform-safe) APIs; if the processor emits JVM-only constructs, non-JVM targets fail to compile.
saying these in an interview costs you the question
- Saying kapt can run on iOS with the right config
- Not knowing non-JVM targets lack javac
- Claiming KSP needs a JVM under the hood for Native
- Confusing KMP source sets with Gradle modules only
- Thinking one global ksp(...) config covers all targets