Conflicting Dependencies When Developing JetBrains Plugins

Posted in Engineering

I am not a Java/Kotlin expert, so there might be some mistakes on my understanding. This was just my understanding of the problem and solution.

I’ve been working on a JetBrains IDE plugin recently, and I ran into a problem with conflicting dependencies. The documentation for developing a JetBrains plugin is there, but its lacking examples of common pitfalls and how to avoid them. This dependency conflict is one of them. I discovered the solution after spending hours trying to figure out why my plugin won’t work, and I found a solution on the Slack channel of JetBrains Platform (which probably gonna be removed soon as the Slack is on free tier and it has limits on how many messages it will retain).

I really like JetBrains IDEs, and I really wish that the documentation for plugin development could be better.

In my case, I need to add ktor client to do HTTP requests, and kotlinx serialization to serialize and deserialize JSON. These libraries requires kotlinx-coroutines-core, but it conflicts with the one that JetBrains IDE bundles. So when there’s a code from JetBrains that requires you to provide a class from kotlinx-coroutines-core, it’s gonna break because now you are passing a class from your own version of kotlinx-coroutines-core instead of the one the IDE bundles.

dependencies {
    val ktor_version = "2.3.12"
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.7.2")
    implementation("io.ktor:ktor-client-content-negotiation:$ktor_version")
    implementation("io.ktor:ktor-serialization-kotlinx-json-jvm:$ktor_version")
    implementation("io.ktor:ktor-client-cio:$ktor_version")
}

Above, I have a few dependencies that are pulling in conflicting versions of the kotlinx-coroutines-core library.

At first, you won’t notice any issue. But the moment you need to use a class from kotlinx-coroutines-core to be called from JetBrains SDK, i.e. Service, you will start to feel the pain.

@Service(Service.Level.PROJECT)
class MyProjectService(project: Project, cs: CoroutineScope) {

    init {
        thisLogger().info(MyBundle.message("projectService", project.name))
        thisLogger().warn("Don't forget to remove all non-needed sample code files with their corresponding registration entries in `plugin.xml`.")
    }

    fun getRandomNumber() = (1..100).random()
}

The cs: CoroutineScope parameter above is from kotlinx.coroutines.CoroutineScope. Now, in compile time, it will use the external library version of kotlinx-coroutines-core pulled by the ktor library, not the one that JetBrains IDE bundles. However, at runtime, JetBrains will complains that it doesn’t understand the CoroutineScope class.

These issues are scattered all over the places, when you need to dispatch a new coroutine, JetBrains will complains that it doesn’t understand how to dispatch it because it was compiled against a different version of kotlinx-coroutines-core than what it bundles.


Special thanks to Riv, Ilan Shamir, and Philip Wedemann, Jaakko Nakaza - their conversation on Slack helped me to figure out the solution.

The solution is to exclude the conflicting library in configurations.runtimeClasspath.


configurations.runtimeClasspath {
    exclude("org.jetbrains.kotlin", "kotlin-stdlib")
    exclude("org.jetbrains.kotlin", "kotlin-stdlib-common")
    exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk8")
    exclude("org.jetbrains.kotlinx", "kotlinx-coroutines")
    exclude("org.jetbrains.kotlinx", "kotlinx-coroutines-core")
    exclude("org.jetbrains.kotlinx", "kotlinx-coroutines-jdk8")
}

This will ensure that the JetBrains IDE will use its own bundled library for kotlinx-coroutines-core instead of the one pulled by ktor.

So far, this solution works for me. If you have a better solution, please let me know.