Compose has two version problems, and the BOM only solves one
The Compose BOM aligns runtime artifacts. The Kotlin/Compose compiler is a separate alignment problem, and mixing the two up produces errors that look like your code is wrong when it isn't.

Day 4 of 100. Days 1–3 covered what Compose is and why it exists. This one is about the part everybody skips: getting versions right, and understanding why the errors you get when they're wrong look nothing like a version problem.
The symptom
You add Compose to a module, sync, and get something like this:
e: This version (1.5.4) of the Compose Compiler requires Kotlin version 1.9.20
but you appear to be using Kotlin version 1.9.22 which is not known to be
compatible.
Or, worse, it compiles fine and then dies at runtime:
java.lang.NoSuchMethodError: No static method
androidx.compose.runtime.ComposerKt.sourceInformation(...)
The first error at least names the problem. The second one doesn't: it looks like a missing method, which reads like a ProGuard issue or a broken dependency, and sends you looking in entirely the wrong place.

Why the obvious fix fails
The instinct is to bump the thing the error names. Kotlin says 1.9.20 is expected, you're on 1.9.22, so you pin the compiler extension to match:
composeOptions {
kotlinCompilerExtensionVersion = "1.5.4"
}
That silences this error and creates the next one, because now you're pinning a compiler version by hand every time you touch Kotlin. Six months later somebody bumps Kotlin, forgets this line exists, and you're back — except now the failure surfaces in CI on a branch nobody's looking at.
The deeper problem is that this fix treats one symptom of a two-part alignment problem as though it were the whole thing.
The actual mechanism
Compose isn't one library. It's a set of independently-versioned artifacts —
compose-ui, compose-foundation, compose-material3, compose-runtime,
compose-ui-tooling, and more. They're released together and expect to be used
together, but Gradle has no idea about that relationship. Nothing stops you resolving
compose-ui:1.6.0 alongside compose-foundation:1.5.4, and nothing warns you when a
transitive dependency quietly upgrades one of them.
That's what the BOM (Bill of Materials) fixes. It's a dependency whose only content is a table of versions. Import it, declare Compose artifacts without versions, and they all resolve to the set that was tested together:
dependencies {
val composeBom = platform("androidx.compose:compose-bom:2026.08.00")
implementation(composeBom)
androidTestImplementation(composeBom)
// No versions here. The BOM supplies them.
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui-tooling-preview")
debugImplementation("androidx.compose.ui:ui-tooling")
}
Here's the part that catches people: the BOM does not version the compiler. The
Compose compiler is a Kotlin compiler plugin. It transforms your @Composable
functions into something the runtime understands, and it has to agree with the Kotlin
compiler it plugs into — a completely different constraint from "which UI artifacts
work together".
So there are two alignment problems:
- Runtime artifacts agree with each other → solved by the BOM.
- The compiler plugin agrees with the Kotlin version → not solved by the BOM.
The NoSuchMethodError above is problem 2 wearing problem 1's clothes. Code compiled
by one compiler version called a runtime method that a differently-versioned runtime
doesn't expose. It shows up at runtime because the compiler had no reason to complain.
The fix
Since Kotlin 2.0 this is much better, because the Compose compiler moved into the Kotlin distribution itself. You apply it as a plugin and it's aligned with your Kotlin version by construction:
// build.gradle.kts (module)
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose) // org.jetbrains.kotlin.plugin.compose
}
android {
buildFeatures { compose = true }
// NOTE: no composeOptions block. The plugin owns compiler alignment now.
}
# gradle/libs.versions.toml
[versions]
kotlin = "2.0.21"
composeBom = "2026.08.00"
[plugins]
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
The version.ref = "kotlin" is the whole point. The compiler plugin version is the
Kotlin version. They cannot drift, because there's one number.
On Kotlin 1.9 and earlier you don't get this, and you do have to pin
kotlinCompilerExtensionVersion by hand against the official compatibility map. If
you're still there, that's a reason to upgrade rather than a reason to maintain the
pin carefully.
What the BOM still doesn't cover
Even with both problems handled, the BOM's reach stops sooner than people expect.
Libraries outside androidx.compose. Accompanist, Coil's coil-compose,
androidx.navigation:navigation-compose, androidx.activity:activity-compose,
androidx.lifecycle:lifecycle-viewmodel-compose — none of these are in the Compose
BOM. They have their own release cadences and their own compatibility expectations
against Compose. navigation-compose in particular tends to be the one that pulls a
newer compose-ui in transitively:
./gradlew :app:dependencies --configuration debugRuntimeClasspath \
| grep -B2 "compose-ui:1"
If you see a Compose artifact arriving as a transitive of something that isn't the BOM, that's the line to look at.
Your own convention plugins. If you've factored Gradle logic into
build-logic/, the BOM has to be applied in the convention plugin, not just in the
app module — otherwise library modules resolve Compose independently and you get the
skew back in exactly the places you stopped looking.
The Android Gradle Plugin. AGP has its own minimum expectations for Compose tooling. It rarely bites, but when it does the message is about a manifest merger or a resource task, which is about as far from "version mismatch" as an error can look.
Reading the resolution output properly
The dependencies task output is dense, and the two things worth training your eye
for are cheap to learn.
An arrow means Gradle overrode a request:
+--- androidx.compose.ui:ui:1.5.4 -> 1.6.8
Something asked for 1.5.4 and got 1.6.8. With a BOM in place that's usually fine — it's the BOM doing its job. Without one, it's version skew happening silently.
A (c) marks a constraint rather than a real dependency, which is exactly what a BOM
contributes:
+--- androidx.compose:compose-bom:2026.08.00
| +--- androidx.compose.ui:ui:1.6.8 (c)
Those (c) lines are the BOM. If you import the BOM and don't see them, the
platform() wrapper is missing and you've added an ordinary dependency on a POM that
does nothing.
How to prove it worked
Don't trust the sync. Ask Gradle what it actually resolved:
./gradlew :app:dependencies --configuration debugRuntimeClasspath | grep "androidx.compose"
Every androidx.compose.* line should show the same version, and any line showing
-> 1.6.0 style coercion is telling you something upgraded a transitive dependency
out from under the BOM. That arrow is the signal worth looking for; a clean list has
none for Compose artifacts.
For the compiler side:
./gradlew :app:dependencies --configuration kotlinCompilerPluginClasspath
The plugin version there should match your Kotlin version exactly.
Two commands, and you've replaced "it synced, probably fine" with an actual answer.
What this generalizes to
The BOM pattern isn't Compose-specific — Firebase, OkHttp and others ship one, and it solves the same class of problem: a family of artifacts that must move together, in a build system that treats them as unrelated.
The more useful generalization is about error messages. NoSuchMethodError names the
method that was missing, which is the last thing that went wrong and rarely the
thing worth fixing. When an error names a symptom two layers below where the decision
was made, the fix that makes the message go away usually isn't the fix.
Tomorrow, Day 5: thinking in Compose — why "the UI is a function of state" changes more about how you write code than it first appears.
This is Day 4 of a 100-day series on Jetpack Compose, working through the official documentation in order. Source for this post: Set up Compose and the BOM to library version mapping.