{"slug": "a-more-reliable-compilation-scheme-for-kotlin-multiplatform-modules", "title": "A More Reliable Compilation Scheme for Kotlin Multiplatform Modules", "summary": "JetBrains introduced an optional \"separate compilation\" scheme for Kotlin Multiplatform in Kotlin 2.5.0-Beta1, enabled by setting kotlin.kmp.separateCompilation=true in gradle.properties. The experimental, off-by-default approach makes compiler results consistent with IDE analysis and more consistently flags problematic calls to library code from common source sets, and it enables incremental compilation for common source sets.", "body_md": "## Kotlin\n\nA concise multiplatform language developed by JetBrains\n\n# A More Reliable Compilation Scheme for Kotlin Multiplatform Modules\n\nThe current compilation approach to Kotlin Multiplatform projects works, but sometimes can lead to unexpected or hard-to-predict behavior. For example:\n\n- The IDE analysis disagrees with the compiler on an overload, type inference, or whether the code should compile at all – with the IDE being **stricter** than the compiler.\n- Your `commonTest` code can unexpectedly call something from a platform source set, breaking your assumptions.\n\nWith Kotlin 2.5.0-Beta1, we introduced an optional “separate compilation” approach to KMP that solves both problems: make the compilation results consistent with IDE analysis, and point more consistently to problematic calls of library code from common source sets. As a bonus, this approach enables us to implement [incremental compilation](https://kotlinlang.org/docs/gradle-compilation-and-caches.html#incremental-compilation) for common source sets.\n\nThe separate compilation feature is [Experimental](https://kotlinlang.org/docs/components-stability.html#stability-levels-explained) and disabled by default. To opt in, add the following compiler option to your `gradle.properties` file (note the [known issues](#known-issues)):\n\n```\nkotlin.kmp.separateCompilation=true\n```\n\nLet’s look closer at the problem and the solution.\n\n## How multiplatform declarations are resolved within a module\n\nWithin a module, multiplatform code performs predictably:\n\n```\n// jvmMain\nfun foo() {}\n\n// commonMain\nfun test() {\n    foo() // Unresolved reference in IDE and during compilation\n}\n```\n\nThe compiler and the IDE agree: The code in `commonMain` is also compiled to other platforms, for example, to Kotlin/JS, and there might not be a `fun foo()` declaration in `jsMain`. [Expect/actual declarations](https://kotlinlang.org/docs/multiplatform/multiplatform-expect-actual.html) exist precisely to solve this problem: explicitly tie platform-specific declarations to common ones.\n\nYou would expect that the reference is also flagged as unresolved when the `foo()` function is declared in a dependency (a module in the same project or a binary artifact like Kotlin standard library or `kotlinx-coroutines`). Unfortunately, with the current compilation scheme that’s where the IDE and the compiler disagree.\n\n## How multiplatform declarations are resolved between modules\n\nTo understand where the problems stem from, let’s look closer at the current compilation setup.\n\n### Kotlin Multiplatform compilation setup\n\nIn a KMP project, a module with common code usually consists of several source sets: in the example above it’s a shared source set (`commonMain`) and a platform source set for a declared target (` jvmMain` for Kotlin/JVM).\n\nWith that setup, the compiler can produce the following artifacts:\n\n- A platform artifact for each platform source set like `jvmMain` –`*.jar` on JVM,`*.klib` on other platforms.\n- A metadata KLIB for each common or intermediate source set (like `commonMain` or`nativeMain` ). A metadata KLIB contains all declarations from the compiled source set without bodies.\n\nSo where do the problems start?\n\n### Common code is compiled against platform artifacts; IDE disagrees\n\nDuring compilation of a platform source set, both the code inside it (`jvmMain`) **and** the code in all relevant shared source sets (` commonMain` and other [intermediate source sets](https://kotlinlang.org/docs/multiplatform/multiplatform-hierarchy.html#default-hierarchy-template)) is compiled against the platform artifacts of its dependencies. Here code from `commonMain` has a chance to implicitly resolve to a `jvmMain` declaration.\n\nIf you expect this to happen, you’re probably fine. It would be more transparent and predictable, however, if `commonMain` could only call declarations from the dependency’s `commonMain` (as listed in the dependency’s metadata KLIB).\n\nThis is exactly what the code analysis in IntelliJ IDEA already assumes. As `jvmMain` declarations are not included in the KLIB metadata, IntelliJ IDEA reports the unresolved reference error when `commonMain` refers to something not declared in common code explicitly:\n\n```\n// lib/jvmMain\nclass Foo\n\n// app/commonMain\nfun main() {\n    Foo() // Unresolved reference in the IDE, no error during compilation\n}\n```\n\nThe same mechanism is also in play with `commonTest` source sets, because tests also “depend” on main code: `commonTest` compiled against `jvmMain` can successfully call a declaration from platform code, instead of being restricted to declarations in `commonMain`. IDE reports the same unresolved reference problem:\n\n```\n// app/jvmMain\nclass Foo\n\n// app/commonTest\nfun main() {\n    Foo() // \"Unresolved reference\" in the IDE, no error during compilation\n}\n```\n\nLet’s see how separate compilation solves these problems.\n\n## How separate compilation solves the problem\n\nKMP separate compilation makes the compiler behave stricter (aligning with the IDE expectation) when compiling common source sets of KMP projects. This makes the overall experience more predictable, although you may need to adjust your old code for stricter compile-time checks.\n\nLet’s look at specific code examples and what happens to them with the switch between compilation schemes.\n\n### Red code in IDE, no error during compilation\n\nWhen you turn on separate compilation, this code stops compiling, because compiler now resolves common code calls strictly using metadata KLIB declarations:\n\n```\n// lib/jvmMain\nclass Foo\n\n// app/commonMain\nfun main() {\n    Foo() // Unresolved reference in IDE, now also a compilation error\n}\n```\n\nThe fix is to show an explicit connection: declare an `expect class` in `lib/commonMain` and make the platform `Foo` class an `actual class`:\n\n```\n// lib/commonMain\nexpect class Foo()\n\n// lib/jvmMain\nactual class Foo\n\n// app/commonMain\nfun main() {\n    Foo() // ok\n}\n```\n\nSame for `commonTest`: with separate compilation, the test code won’t resolve to a platform call in `jvmMain`. If that’s what you want to test, declare the corresponding `expect` in `commonMain`:\n\n```\n// app/jvmMain\nactual class Foo\n\n// app/commonMain\nexpect class Foo\n\n// app/commonTest\nfun main() {\n    Foo() // Ok, calls Foo from jvmMain\n}\n```\n\n### IDE and compiler don’t agree on selected overload\n\nWhen several overloads are declared in `lib/commonMain` and `lib/jvmMain`, the IDE currently interprets the call from `app/commonMain` differently from the compiler:\n\n```\n// lib/commonMain\nfun foo(x: Any) = \"common\"\n\n// lib/jvmMain\nfun foo(x: String) = \"platform\"\n\n// app/commonMain\nfun main() {\n    // \"Go to declaration\" on foo() jumps to lib/commonMain,\n    // but at runtime \"platform\" is printed\n    println(foo(\"\"))\n}\n```\n\nThe IDE resolves the `foo(\"\")` call to the declaration `fun foo(x: Any)` in `lib/commonMain`, as it assumes that common code can only depend on common declarations. But during the actual compilation `app/commonMain` currently sees both declarations and chooses the more specific overload `fun foo(x: String)`. At runtime, “platform” is printed.\n\nWith the new compilation scheme, the call is resolved to `fun foo(x: Any)` in `lib/commonMain` and at runtime, and “common” is printed.\n\n### Unexpected type inference\n\nA trickier problem can occur when the error is triggered not at the `app/commonMain` call site, but in platform code. In the following example the IDE actually sees no problem, but the JVM compilation fails.\n\n`actual` classes are allowed to have different supertypes than their `expect` counterparts, so you can reasonably arrive at the following code structure:\n\n```\n// eventsLib/commonMain\ninterface Event { val name: String }\n\nexpect class ClickEvent(target: String) : Event { override val name: String }\nexpect class ScrollEvent(offset: Int) : Event { override val name: String }\n\n// eventsLib/jvmMain — serialized events get queued or stored in a session\nactual class ClickEvent actual constructor(target: String) : Event, Serializable { /* ... */ }\nactual class ScrollEvent actual constructor(offset: Int) : Event, Serializable { /* ... */ }\n\n// app/commonMain\nfun currentEvent() = if (clicked()) ClickEvent(\"buy\") else ScrollEvent(120)\n\nfun report() = currentEvent().name // e: Unresolved reference 'name' only on the JVM\n```\n\nThe IDE only matches `commonMain` declarations, which allows it to (correctly) infer the return type of `currentEvent()` as `Event` and resolve the `.name` reference.\n\nOn JVM, however, the library declares an additional supertype for each `actual` class. When compiling `app` for the JVM, the compiler currently resolves common code against the `eventsLib` JAR which has the `eventsLib/jvmMain` version of the classes. With the two supertypes, the return type inference for the `currentEvent()` call arrives at `Any`, which doesn’t have a `.name`, and the JVM compilation fails.\n\nWith the separate compilation enabled, `app/commonMain` code is resolved only against `eventsLib/commonMain` declarations. So when compiling the JVM target of `app`, the compiler already has the resolved common code and has inferred the `Event` return type for the `currentEvent()` function. The JVM compilation runs separately, doesn’t have to infer anything, and finishes successfully.\n\n## How to enable separate compilation\n\nThe separate compilation feature is [Experimental](https://kotlinlang.org/docs/components-stability.html#stability-levels-explained) and disabled by default. To opt in, add the following compiler option to your `gradle.properties` file (note the known issues below):\n\n```\nkotlin.kmp.separateCompilation=true\n```\n\n## Known issues\n\nFor separate compilation to work correctly in KMP projects, it is important that authors of multiplatform libraries publish the metadata KLIBs. This is the default behavior for publishing tasks of the KMP Gradle plugin, but the new compilation scheme makes metadata KLIBs essential.\n\nThere are some known issues with KMP separate compilation that are still being fixed:\n\n- Some problems with commonization of cinterops (like [KT-88178](https://youtrack.jetbrains.com/issue/KT-88178) or[KT-41509](https://youtrack.jetbrains.com/issue/KT-41509) ). These are unlikely to affect an average KMP project, but enable the separate compilation mode with caution if you use[C interoperability](https://kotlinlang.org/docs/native-c-interop.html) .\n- KMP modules with common source sets and a single declared target are **not** affected by separate compilation for now.\n- Various smaller issues such as [KT-88148](https://youtrack.jetbrains.com/issue/KT-88148) .\n\n#### Subscribe to Kotlin Blog updates", "url": "https://wpnews.pro/news/a-more-reliable-compilation-scheme-for-kotlin-multiplatform-modules", "canonical_source": "https://blog.jetbrains.com/kotlin/2026/09/a-more-reliable-compilation-scheme-for-kotlin-multiplatform-modules/", "published_at": "2026-09-28 09:38:26+00:00", "updated_at": "2026-09-28 11:19:12.378859+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["JetBrains", "Kotlin", "Kotlin Multiplatform", "Kotlin 2.5.0-Beta1", "IntelliJ"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/a-more-reliable-compilation-scheme-for-kotlin-multiplatform-modules", "markdown": "https://wpnews.pro/news/a-more-reliable-compilation-scheme-for-kotlin-multiplatform-modules.md", "text": "https://wpnews.pro/news/a-more-reliable-compilation-scheme-for-kotlin-multiplatform-modules.txt", "jsonld": "https://wpnews.pro/news/a-more-reliable-compilation-scheme-for-kotlin-multiplatform-modules.jsonld"}}