Teams are migrating to Kotlin 2 and discovering their Jetpack Compose builds are still wired for the old world: a separately versioned Compose compiler. The result is predictable CI breakage, hard-to-debug classpath mismatches, and upgrades that stall because nobody is sure which knob now controls what.
Kotlin 2.0+ moved the Compose compiler into the Kotlin repository and made it ship alongside Kotlin; the “Compose compiler version” is no longer a thing you manage independently in the typical Android build. That’s good news, but it changes the repo-level shape of your build, and the migration guides don’t cover the edge cases that show up in real multi-module repos. (kotlinlang.org)
What actually changed (and why your old Gradle still “works” until it doesn’t)
Historically, the Compose compiler was published and versioned separately, and you had to keep it compatible with your Kotlin version (hence the familiar “Compose Compiler requires Kotlin version …” failures). (stackoverflow.com)
Since Kotlin 2.0.0, the Compose compiler is merged into Kotlin and the supported setup is to apply the Compose Compiler Gradle plugin org.jetbrains.kotlin.plugin.compose, with a version that matches Kotlin. (kotlinlang.org)
Two knock-on effects matter in practice:
- You stop configuring `kotlinCompilerExtensionVersion` as the primary lever for Kotlin 2+ projects, and can usually remove it. (android-developers.googleblog.com)
- The plugin graph becomes the migration surface area, not just dependency versions. Many repos fail because a module applies the “wrong” Compose-related plugin (or applies the right one in the wrong place).
If your build is a mix of Kotlin 1.x and 2.x, or mixes Android-only Compose with Compose Multiplatform conventions, you can end up in a half-migrated state where Gradle sync succeeds but CI fails on specific variants.
The Android migration checklist (repo-level, not sample-app level)
This is the order that tends to keep teams out of dead ends.
1) Decide: are you actually migrating to Kotlin 2 yet?
This sounds obvious, but teams often “upgrade Compose” and accidentally pull in Kotlin 2 transitive expectations.
- If you are not ready for Kotlin 2, do not apply
org.jetbrains.kotlin.plugin.composeyet. - If you are ready, commit to the idea that the Compose compiler is coupled to Kotlin and treat Kotlin as the controlling version axis. (kotlinlang.org)
2) Make Kotlin the single source of truth
In real repos, Kotlin version drift happens via:
- build-logic / convention plugins
- root
plugins {}block buildscript { dependencies { classpath(...) } }legacy usagepluginManagementinsettings.gradle(.kts)
Pick one mechanism and eliminate the others.
If you use a Version Catalog, keep it explicit:
- Define a single
kotlinversion. - Apply
org.jetbrains.kotlin.android/org.jetbrains.kotlin.jvm/org.jetbrains.kotlin.multiplatformconsistently. - Add
org.jetbrains.kotlin.plugin.composewithversion.ref = "kotlin". (kotlinlang.org)
The goal is that a reviewer can answer “what Kotlin are we on?” by reading one file.
3) Apply the Compose compiler plugin only where it’s needed
The plugin should be applied to modules that compile Compose code.
- Apply
org.jetbrains.kotlin.plugin.composeto Android app modules and Android library modules that contain@Composablecode. - Do not apply it to pure Kotlin modules that don’t contain Compose (you will slow down builds and increase surface area for plugin interactions).
Android’s own setup docs explicitly show the plugin being applied and versioned with Kotlin. (developer.android.com)
4) Remove composeOptions.kotlinCompilerExtensionVersion (carefully)
The official migration direction is that this is no longer required and can be removed. AGP’s API docs also indicate it will be ignored in favour of the Kotlin compiler version set in the build. (android-developers.googleblog.com)
Practical guidance:
- Remove it from modules where you’ve applied
org.jetbrains.kotlin.plugin.compose. - If you have internal Gradle code that still reads a
composeCompilerVersionproperty, delete it rather than keeping a “dead” property that misleads future upgrades.
5) Re-audit your Compose dependency story (compiler vs libraries)
A common confusion is assuming “Compose ships with Kotlin now”. Only the compiler plugin does.
You still need to manage:
- Compose runtime/UI libraries via AndroidX (typically via the Compose BOM)
- Material / foundation / tooling dependencies
Android’s documentation still emphasises that adopting newer Compose releases can pull you into newer Android Gradle Plugin requirements (because compileSdk and AGP constraints move too). (developer.android.com)
The practical trade-off: upgrading Kotlin/Compose often becomes a three-way lockstep between Kotlin, AGP, and AndroidX Compose libraries.
6) Standardise Compose compiler options in one place
If you are setting Compose compiler feature flags or metrics, move to the Kotlin Compose compiler options approach rather than scattering free-form kotlinOptions across modules.
Kotlin documents a dedicated Compose compiler options DSL and feature flags for the plugin. (kotlinlang.org)
Rule of thumb:
- Put flags behind Gradle properties where possible.
- Avoid per-module divergence unless you have a concrete reason (for example: a library module where you need stricter stability warnings).
7) CI validation: add one “Compose compiler sanity” job
Most teams rely on ./gradlew test or assembleDebug and miss failures that only show up in release, specific build flavours, or when configuration cache is enabled.
Add a CI job that explicitly does:
./gradlew :app:assembleDebug :app:assembleRelease./gradlew lint(or:app:lintReleasedepending on your setup)- A clean build from a fresh Gradle cache at least once per day (or per merge queue batch)
You’re looking to catch classpath mismatches and plugin resolution issues before they become “works on my machine”.
The two repeating build failures (and how to detect them early)
Across migrations, two failure patterns keep turning up because they’re symptoms of a repo being half in the old model.
Failure mode 1: “Compose Compiler requires Kotlin version …” (a version axis mix-up)
You will still see variants of:
- “This version (x.y.z) of the Compose Compiler requires Kotlin version a.b.c …” (stackoverflow.com)
Even though the Compose compiler is “in Kotlin” now, you can trigger this by:
- applying the Compose compiler plugin inconsistently across modules
- pinning an old compiler extension version in
composeOptionswhile also upgrading Kotlin - pulling in older build logic that still adds a Compose compiler artefact explicitly
Early detection:
- Add a build scan or Gradle task that prints the resolved Kotlin Gradle plugin version for each subproject and fails if more than one version exists.
- Search your repo for these strings and delete the configuration, not just comment it out:
kotlinCompilerExtensionVersionandroidx.compose.compiler:compilerkotlin-compose-compiler-plugin-embeddable
What to do when you hit it:
- If you’re on Kotlin 2.0+, stop trying to “pick a Compose compiler version”. Apply
org.jetbrains.kotlin.plugin.composeand make its version equal to your Kotlin version. (kotlinlang.org) - If you have to stay on Kotlin 1.x temporarily, use the official compatibility mapping for Kotlin/Compose compiler, and plan the Kotlin 2 migration as a discrete step. (developer.android.com)
When the right answer is “do not do this”:
- Do not add
suppressKotlinVersionCompatibilityCheck-style workarounds as a long-term fix. If your compiler and Kotlin disagree, you are compiling code with an unsupported toolchain, and you will burn time on undefined behaviour later.
Failure mode 2: “Could not resolve … kotlin-compose-compiler-plugin-embeddable” / plugin classpath resolution errors
This shows up as Gradle resolution failures against configurations like kotlinCompilerPluginClasspathDebug (often in multi-module or third-party SDK scenarios). (stackoverflow.com)
It tends to happen when:
- a dependency or plugin expects the old embeddable artefact coordinate
- your repository list / dependency resolution management blocks a required repository
- your build uses variant-aware resolution and hits a “no matching variant” type error path (often after a Gradle or Kotlin plugin upgrade)
Early detection:
- In CI, run with
--refresh-dependenciesoccasionally on a clean environment to catch repository/proxy issues. - Add a check that your
dependencyResolutionManagementrepositories are consistent across settings/build-logic (avoid “it resolves locally because Android Studio added something”).
What to do when you hit it:
- Verify you are on the modern plugin-based setup from Android’s docs (“Set up the Compose Compiler Gradle plugin”) and remove direct references to the old compiler artefact if they exist. (developer.android.com)
- If the failure originates inside a third-party Gradle plugin or SDK, you may need to:
- upgrade that dependency
- isolate it into a module with legacy settings
- or, if it’s unmaintained, replace it
If you’re seeing generic “cannot choose between variants” style errors after upgrades, Kotlin documents how Gradle plugin variants participate in resolution; it’s often a sign you’re mixing incompatible Gradle/Kotlin plugin expectations across included builds. (kotlinlang.org)
What most teams miss: migration is a build-logic refactor, not a dependency bump
The migration guide reads like a small edit to build.gradle, but in a 20–500 person org it’s usually a build-logic change:
- convention plugins need updating
- version catalog entries need pruning
- CI needs a new “toolchain coherence” check
- documentation for engineers needs rewriting (“how to upgrade Compose” is now mostly “how to upgrade Kotlin + AGP safely”)
The most cost-effective thing you can do is make the build tell you when you’ve drifted.
Here’s a minimal guardrail set that pays off quickly:
- One place to set Kotlin version
- Compose compiler plugin applied consistently to Compose modules
- A Gradle task that fails if multiple Kotlin Gradle plugin versions are on the classpath
- CI builds both debug and release variants for at least one representative app
Where codeversols tends to help (briefly, and honestly)
If you’re mid-migration and your repo has a build-logic module, custom convention plugins, multiple apps, and a mix of View + Compose UI, the risk is not “we can’t follow the guide” — it’s that you end up with a fragile toolchain nobody wants to touch. This is the kind of work where we (codeversols) usually help by making the build repeatable: simplifying plugin application, aligning Kotlin/AGP/Compose dependencies, and adding CI checks so upgrades stop being a fire drill.
Close
The Compose compiler moving into Kotlin is a net win: fewer compatibility spreadsheets, fewer mystery compiler artefacts, and a clearer upgrade story. But it also forces a decision: either you run the new plugin model cleanly, or you carry a half-migrated build that will keep breaking in the same two ways.
Treat this migration as a small build-system refactor with explicit guardrails, not a “bump Kotlin and see what happens” change, and you’ll get back to shipping UI rather than debugging Gradle.



