Dev48
Language
  • About
  • Services
  • Industries
  • Technologies
  • Articles
  • Contacts
Book a call
    Home/Articles/A more reliable compilation scheme for kotlin multiplatform modules
Dev48

© 2026 · All rights reserved.

A More Reliable Compilation Scheme for Kotlin Multiplatform Modules

Источник: The JetBrains Blog

A More Reliable Compilation Scheme for Kotlin Multiplatform Modules

Source: The JetBrains Blog

The current compilation approach to Kotlin Multiplatform projects works, but sometimes can lead to unexpected or hard-to-predict behavior. For example: With Kotlin 2.5.0-Beta1, we introduced an optional “separate compilation” approach to KMP that solves both problems: make the…

September 28, 2026•Updated: September 28, 2026

A More Reliable Compilation Scheme for Kotlin Multiplatform Modules

Read this post in other languages:

The current compilation approach to Kotlin Multiplatform projects works, but sometimes can lead to unexpected or hard-to-predict behavior. For example:

  • 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.
  • Your commonTest code can unexpectedly call something from a platform source set, breaking your assumptions.

With 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 for common source sets.

The separate compilation feature is Experimental and disabled by default. To opt in, add the following compiler option to your gradle.properties file (note the known issues):

Let’s look closer at the problem and the solution.

How multiplatform declarations are resolved within a module

Within a module, multiplatform code performs predictably:

The 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 exist precisely to solve this problem: explicitly tie platform-specific declarations to common ones.

You 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.

How multiplatform declarations are resolved between modules

To understand where the problems stem from, let’s look closer at the current compilation setup.

Kotlin Multiplatform compilation setup

In 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).

With that setup, the compiler can produce the following artifacts:

  • A platform artifact for each platform source set like jvmMain – *.jar on JVM, *.klib on other platforms.
  • 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.

So where do the problems start?

Common code is compiled against platform artifacts; IDE disagrees

During 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) is compiled against the platform artifacts of its dependencies. Here code from commonMain has a chance to implicitly resolve to a jvmMain declaration.

If 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).

This 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:

The 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:

Let’s see how separate compilation solves these problems.

How separate compilation solves the problem

KMP 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.

Let’s look at specific code examples and what happens to them with the switch between compilation schemes.

Red code in IDE, no error during compilation

When you turn on separate compilation, this code stops compiling, because compiler now resolves common code calls strictly using metadata KLIB declarations:

The fix is to show an explicit connection: declare an expect class in lib/commonMain and make the platform Foo class an actual class:

Same 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:

IDE and compiler don’t agree on selected overload

When several overloads are declared in lib/commonMain and lib/jvmMain, the IDE currently interprets the call from app/commonMain differently from the compiler:

The 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.

With the new compilation scheme, the call is resolved to fun foo(x: Any) in lib/commonMain and at runtime, and “common” is printed.

Unexpected type inference

A 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.

actual classes are allowed to have different supertypes than their expect counterparts, so you can reasonably arrive at the following code structure:

The IDE only matches commonMain declarations, which allows it to (correctly) infer the return type of currentEvent() as Event and resolve the .name reference.

On 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.

With 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.

How to enable separate compilation

The separate compilation feature is and disabled by default. To opt in, add the following compiler option to your gradle.properties file (note the known issues below):

Known issues

For 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.

There are some known issues with KMP separate compilation that are still being fixed:

  • Some problems with commonization of cinterops (like KT-88178 or KT-41509). These are unlikely to affect an average KMP project, but enable the separate compilation mode with caution if you use C interoperability.
  • KMP modules with common source sets and a single declared target are not affected by separate compilation for now.
  • Various smaller issues such as KT-88148.

Send feedback

We’d be grateful for any feedback you have on separate compilation: leave a comment in the dedicated YouTrack issue.

← All articles

More in Software Development

All →
Air Teams: Bring Your Best Agentic Workflows to the Whole Team – and Automate Repeatable Work
JetBrains

Air Teams: Bring Your Best Agentic Workflows to the Whole Team – and Automate Repeatable Work

Rider 2026.2.3 Is Released!
JetBrains

Rider 2026.2.3 Is Released!

BOB in Hotel Industry: Business on Books & OTB Guide
Hotelogix

BOB in Hotel Industry: Business on Books & OTB Guide

Sennheiser Momentum 5 review: Great sound, incredible battery life, and few compromisesПресса
Momentum

Sennheiser Momentum 5 review: Great sound, incredible battery life, and few compromises

Boeing flags 737 Max software glitch affecting some automated approach functionsПресса
Boeing

Boeing flags 737 Max software glitch affecting some automated approach functions

Apple faces $5.7 billion patent infringement verdict over iPhone and Apple Watch hapticsПресса
Apple

Apple faces $5.7 billion patent infringement verdict over iPhone and Apple Watch haptics

More from JetBrains

Air Teams: Bring Your Best Agentic Workflows to the Whole Team – and Automate Repeatable Work
JetBrains

Air Teams: Bring Your Best Agentic Workflows to the Whole Team – and Automate Repeatable Work

Rider 2026.2.3 Is Released!
JetBrains

Rider 2026.2.3 Is Released!

Continuing to Move PHP Open Source Forward
JetBrains

Continuing to Move PHP Open Source Forward