How do Kotlin compiler plugins differ from KSP/kapt symbol processors, and what can a compiler plugin do that a processor cannot? Give examples.
answer
- Processors add files; plugins transform bytecode
- Plugins hook FIR (frontend) + IR (backend) in K2
- all-open/no-arg for Spring/JPA, serialization, Compose, Parcelize
- Plugins use unstable compiler-internal APIs
- Applied via kotlin("plugin.*") in plugins {}
basics
~20 sSymbol processors can only read your code and generate new files. Compiler plugins hook inside the compiler and can change the actual bytecode of existing code — adding methods, rewriting bodies, or generating synthetic members. They are more powerful but more invasive.
solid answer
~50 sKSP/kapt are read-and-generate: they inspect declarations and emit new source files, but cannot alter existing ones. A Kotlin compiler plugin runs inside kotlinc itself, with access to compiler internals (FIR in the K2 frontend and the IR backend), so it can synthesize new declarations on existing classes, transform method bodies, and emit different bytecode. That is why features that must change code are plugins, not processors: all-open and no-arg (Spring/JPA support, opening classes and adding no-arg constructors), kotlin-serialization (generates serializers and a synthetic companion), Parcelize, the Compose compiler (rewrites @Composable functions), and the Lombok/Power-assert plugins. Compiler plugins are applied via the Gradle plugin DSL (e.g. plugins { kotlin("plugin.serialization") }). The trade-off: plugins are tied to compiler-internal APIs that change between versions, so they are riskier to write and maintain than KSP processors, which sit on a stable public API.
code
kotlin · 10 linesplugins {
kotlin("jvm") version "2.1.0"
kotlin("plugin.serialization") version "2.1.0" // compiler plugin: generates serializers
kotlin("plugin.spring") version "2.1.0" // all-open: opens @Component classes
kotlin("plugin.jpa") version "2.1.0" // no-arg: synthesizes no-arg ctor for @Entity
}
@kotlinx.serialization.Serializable
data class User(val id: Long, val name: String)
// serialization plugin injects a synthetic KSerializer + companion - a processor could notgo deeper
Knows compiler plugins are something that hooks into the compiler, beyond just generating files.
States the key difference: processors only generate new files, plugins can modify existing code/bytecode; names a couple of plugin examples.
Explains FIR/IR hooks, gives concrete plugins (all-open, no-arg, serialization, Compose) and why each must be a plugin, and the unstable-API trade-off.
Reasons about maintenance cost of compiler-internal APIs across Kotlin upgrades and when to absorb plugin risk vs stay on KSP.
## Two different powers | Capability | KSP / kapt (symbol processors) | Compiler plugins | |---|---|---| | Runs | Around the compiler | **Inside** the compiler | | Can read code | Yes | Yes | | Can **generate new files** | Yes | Yes | | Can **modify existing code / bytecode** | **No** | **Yes** | | API stability | Stable public API | Compiler-internal (changes per version) | **Symbol processors** (KSP, kapt) are strictly *additive*: they look at your declarations and write **new** source files. They cannot open a `final` class, add a constructor to an existing class, or rewrite a function body. **Compiler plugins** run *inside* `kotlinc`. In the K2 architecture they can hook the **FIR** frontend (to declare/resolve synthetic members) and/or the **IR** backend (to transform the intermediate representation that becomes bytecode). That lets them do things processors cannot. ## What plugins do that processors cannot — real examples - **all-open** — makes annotated classes (and members) `open` without you typing `open`. Used by **Spring** (CGLIB proxies need open classes). Modifies existing classes. - **no-arg** — synthesizes a **no-argument constructor** for annotated classes. Used by **JPA**/Hibernate entities. - **kotlinx-serialization** — generates a `KSerializer` and injects a synthetic `companion`/serializer into `@Serializable` classes; rewrites code, not just adds files. - **Compose compiler** — rewrites `@Composable` functions to thread the `Composer` and enable recomposition. - **Parcelize** — generates `Parcelable` implementation directly on the annotated class. - **kotlin-power-assert** — instruments assertion call sites to produce rich messages. ## How you apply them Most are exposed as Gradle plugins: ```kotlin plugins { kotlin("jvm") version "2.1.0" kotlin("plugin.serialization") version "2.1.0" // serialization compiler plugin kotlin("plugin.spring") version "2.1.0" // all-open preset for Spring kotlin("plugin.jpa") version "2.1.0" // no-arg preset for JPA } ``` Under the hood these register a compiler plugin artifact with `kotlinc`. ## The trade-off - Compiler plugins are **more powerful** (transform existing code) but depend on **compiler-internal APIs** (FIR/IR) that evolve with each Kotlin release — they must be rebuilt/tested against new versions. The Compose plugin's version even tracks the Kotlin version closely. - KSP processors sit on a **stable, public** API and only generate — safer and simpler when generation suffices. ## Rule of thumb - Need to **generate** companion code? Use **KSP**. - Need to **change** existing classes/bytecode (open them, add constructors, rewrite bodies)? You need a **compiler plugin**.
- Why can't kotlinx.serialization be implemented purely with KSP?It must inject a synthetic serializer into the @Serializable class and wire up companion access — modifying the existing class. KSP can only generate separate files, so serialization ships as a compiler plugin.
- Why does the Compose compiler plugin version track the Kotlin version so tightly?It transforms IR using compiler-internal APIs that change between Kotlin releases, so each Kotlin version generally needs a matching Compose compiler build.
A symbol processor is an author writing a new chapter; a compiler plugin is an editor who can rewrite the sentences already on the page.
saying these in an interview costs you the question
- Claiming KSP can open final classes or add constructors
- Saying compiler plugins and KSP are interchangeable
- Not knowing all-open/no-arg are compiler plugins behind Spring/JPA support
- Believing compiler plugins use a stable public API like KSP
- Thinking serialization/Compose are 'just annotation processors'