Kotlin 2.5.0-Beta1 ships Kotlin Multiplatform separate compilation, an Experimental mode that makes the compiler resolve common code the way the IDE already does. In JetBrains' 28 September post it's off by default. Try it on a branch, not in CI. Some code that compiles today will stop compiling, and one case changes behaviour without any error at all.
It's part of our series Platform and language release notes: what breaks and what to fix.
What separate compilation changes
Today the compiler builds common code against your dependencies' platform artifacts: the jvmMain jar on the JVM, klibs on other targets. That means commonMain can quietly resolve a call to a declaration that only exists in jvmMain. The IDE doesn't do that. It analyses common code against common declarations only, so the two disagree, and the post says the IDE ends up "stricter than the compiler."
With kotlin.kmp.separateCompilation=true, the compiler resolves common code calls "strictly using metadata KLIB declarations." A metadata KLIB holds all declarations from a compiled source set without bodies. Common code sees the common API and nothing else.
JetBrains names two problems this fixes. IDE and compiler can disagree on an overload, on type inference, or on whether code should compile at all. And your commonTest code can unexpectedly call something from a platform source set.
Which code stops compiling
Four cases, all from the same post. The first two become compile errors, the third changes output with no error, and the fourth gets better.
| Symptom | Cause | Fix |
|---|---|---|
Unresolved reference in commonMain of the app | Common code called a class that lives only in a library's jvmMain | expect in the library's common source set, actual in jvmMain |
Unresolved reference in commonTest | Test called a class from its own module's jvmMain | Declare expect in commonMain, actual in jvmMain |
| Different output, no error | Common call now picks the common overload | Compare test output, then rename or restructure the overloads |
| Inference error on JVM disappears | Inference no longer lands on Any | None needed |
Platform declaration used from commonMain
A library has class Foo in its jvmMain. The app calls it from common code:
fun main() {
Foo()
}The IDE flags an unresolved reference. The old compiler accepted it because it saw the JVM artifact. Now it's a compile error too. The fix is to declare expect class Foo() in the library's commonMain and keep an actual class Foo in jvmMain. Per the expect/actual docs, every expected declaration needs a matching actual in every platform source set, so every other target needs its own actual too. If expect and actual are new to your team, our CTO's guide to Kotlin Multiplatform covers how they tie common code to platform code.
commonTest calling platform code
The same thing happens inside one module. class Foo sits in the app's jvmMain, and commonTest does Foo(). That used to compile, and now it fails. The post's fix is the same pair: expect class Foo in commonMain, actual class Foo in jvmMain. After that, commonTest compiles and calls the jvmMain implementation.
If you have a lot of these, treat them as a finding rather than a nuisance. A "common" test that only works because it reaches into JVM code isn't testing your common contract.
Overloads that silently change
This is the case to read twice. The library has this in commonMain:
fun foo(x: Any) = "common"and this in jvmMain:
fun foo(x: String) = "platform"The app's commonMain then runs println(foo("")). With the old scheme it prints platform, even though the IDE navigated to the Any overload. With separate compilation on, it prints common.
The post shows no error for this case and does not mention a warning. Your build goes green and a code path does something different. If your common code calls into libraries or modules that overload across common and platform source sets, your tests are the only safety net, so run them and diff the results rather than trusting a clean build.
One inference failure that goes away
The inverse case is kinder. In the post's example, an Event hierarchy has ClickEvent and ScrollEvent, and the platform actual classes add Serializable. A function returns if (clicked()) ClickEvent("buy") else ScrollEvent(120), and currentEvent().name fails on the JVM with "Unresolved reference 'name'" because inference lands on Any. The IDE was fine with it. With the mode on, it compiles. So some of your current workarounds may become unnecessary.
How to try separate compilation on a branch
1. Create a branch
Keep the experiment off your main line.
git checkout -b kmp-separate-compilation2. Move the Kotlin plugin to 2.5.0-Beta1
In your plugins block, set the Kotlin version to the Beta, as Kotlin's EAP build setup page describes:
plugins {
kotlin("multiplatform") version "2.5.0-Beta1"
}That page also warns that an EAP Kotlin version "may not work with the libraries of the previously released version," and gives org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0 as an example of a kotlinx library version compatible with 2.5.0-Beta1. Bump your kotlinx dependencies if the build complains.
3. Turn the mode on
Add one line to gradle.properties:
kotlin.kmp.separateCompilation=true4. Build every target and every test
Run your normal full build. This command is ordinary Gradle, not something the post prescribes:
./gradlew buildThe post's examples produce new errors in common source sets and in commonTest; cinterop projects may see other problems (see known issues below).
5. Fix each error, then check overload call sites
Apply the two expect/actual patterns above, or move the call into platform code if it never belonged in common. Then go looking for the case that won't error: common calls to functions that also have a platform overload. Compare test results on the branch against your main line.
6. Report what you found
Add your results to JetBrains' public feedback thread, covered below.
What it does not give you yet
The post says the new scheme "enables us to implement incremental compilation for common source sets." That's the stated direction, not a shipped feature, and the post gives no build-time numbers. Don't enable this flag expecting faster builds, and don't promise anyone that it will.
A few limits from the post's known issues:
- KMP modules with common source sets and a single declared target aren't affected by separate compilation for now.
- Projects using C interoperability should "enable the separate compilation mode with caution." JetBrains points to cinterop commonization problems tracked as KT-88178 and KT-41509.
- Libraries you consume need to publish metadata KLIBs. That's the default for the KMP Gradle plugin's publishing tasks, and the post says the new scheme makes them essential. If you publish libraries yourself, check that nothing in your setup turns them off. Our piece on how JetBrains' new toolchain publishes KMP libraries covers library publishing in the new toolchain.
Why wait for stable before a CI rollout
The flag is Experimental, and it only exists in a Beta compiler. The Kotlin v2.5.0-Beta1 changelog already lists separate-compilation fixes such as KT-88770 (a false-positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error) and KT-88192 (incompatibility with the Gradle configuration cache). KT-88148 (an annotation on a property is misplaced if the actual annotation has only a field target) was still open on 3 October.
Kotlin 2.5.0 stable is planned for December 2026, on an approximate schedule. Our opinion: keep your required CI on 2.4.20 (see the 2.4.20 migration notes) and run the beta branch as a non-blocking job. You get early warning about the errors above without letting a Beta compiler gate your releases.
Reporting problems
JetBrains opened a dedicated YouTrack issue, KT-89725, "Separate compilation public feedback". Comment there with a minimal reproduction. Before you do, check the known issues above, since cinterop and single-target behaviour are already documented.
FAQ
Is separate compilation on by default?
No. It's Experimental and disabled by default in Kotlin 2.5.0-Beta1. You opt in with kotlin.kmp.separateCompilation=true in gradle.properties. The post says it was introduced in Kotlin 2.5.0-Beta1.
Does it speed up my build?
The post doesn't claim it does. It says the approach makes incremental compilation for common source sets possible to implement, which is a future capability, and it gives no timings.
Do I need to change library dependencies?
The libraries you depend on must publish metadata KLIBs. The KMP Gradle plugin's publishing tasks do that by default, but the post does not say how many libraries publish them. Check the libraries you depend on. Your own code is where changes show up.
Will my single-target module change?
Not for now. The post says KMP modules with common source sets and a single declared target aren't affected by separate compilation yet.
When is Kotlin 2.5.0 stable?
Planned for December 2026, per the approximate schedule on the Kotlin releases page.
