Bringing JProfiler to VS Code with Kotlin Multi-Platform

2025-04-16
Posted by Ingo Kegel

At ej-technologies, we've long provided JProfiler integrations for Java IDEs like IntelliJ, Eclipse, and NetBeans. These plugins share a significant amount of logic—profiler configuration, session management, and communication with the JProfiler backend—all written in Kotlin.

When we decided to add support for VS Code, we faced a problem: VS Code extensions run on Node.js, not the JVM. Rewriting the plugin from scratch in TypeScript would have been inefficient. Instead, we turned to Kotlin Multi-Platform (KMP), allowing us to reuse our existing Kotlin code while adapting to the JavaScript ecosystem.

The Challenge of Cross-Platform Code

To support platform-specific functionality in shared code, Kotlin Multi-Platform uses the expect/actual mechanism. In the common source set, an expect declaration defines a function, class, or property without an implementation. Then, in each platform-specific source set (JVM, JS, etc.), an actual declaration provides the corresponding implementation. This lets the shared code call into platform-specific functionality without relying on reflection or dynamic dispatch.

Blog figure

The core of our plugin was already in Kotlin but moving it to common Kotlin code meant replacing all JVM-specific dependencies.

Replacing the JRE

For example, Kotlin common code can't rely on java.io for file access or java.lang.ProcessBuilder for OS processes. To migrate these two APIs, we used:

  • kotlinx-io for file operations and I/O
  • kmp-process for process management (a critical piece, since JProfiler launches external processes)

For example, consider the following JVM code that writes some text to a file:

java.io.File("file.txt").writeText("Hello")

With kotlinx-io, we defined the following extension function to achieve the same effect:

// Multi-platform file write using kotlinx-io
fun kotlinx.io.files.Path.writeText(text: String) {
    kotlinx.io.files.SystemFileSystem.sink(this).buffered().use { it.writeText(text) }
}

For anyone migrating JVM code to Kotlin common code, JetBrains offers a useful site for finding Kotlin Multi-Platform libraries: https://klibs.io.

These libraries worked as expected but required careful adjustments, especially around error handling and platform-specific quirks.

Serialization: From Kryo to kotlinx.serialization

Our existing plugins used Kryo for communication between the IDE and JProfiler. Kryo relies on JVM functionality. To support Kotlin/JS, we migrated to kotlinx.serialization.

This wasn't just a drop-in replacement. Kryo's runtime reflection and kotlinx.serialization's compile-time approach demanded changes to our data classes. However, we managed to annotate them in a way that both frameworks could serialize the same objects, ensuring backward compatibility with older JProfiler versions.

Coroutines Instead of Threads

The original plugin used multithreading for tasks like background communication with the profiler. In KMP, we replaced this with Kotlin coroutines, which work seamlessly across JVM and JavaScript.

This shift wasn't trivial. Coroutines require a different mindset—but the result is clean and maintainable.

Adapting jclasslib for Multi-Platform

In our plugin code, we need to read class files to resolve line numbers and other debug information. We use the jclasslib library for this purpose, which we maintain in-house. As part of the KMP migration, we adapted jclasslib's data structures module into a Kotlin Multi-Platform library. This allowed us to keep the same class file parsing logic while making it available to both our JVM and Node.js platforms.

That migration can be seen in this single commit and mostly involves getting rid of Java-specific class references and migrating I/O to kotlinx-io. Note that we only migrated the data structures module. Migrating the bytecode viewer would be more challenging, but with Compose Multiplatform, it looks feasible.

As an upside, jclasslib is now also listed on klibs.io, so you can use it in your own Kotlin Multi-Platform projects.

Bridging Kotlin and TypeScript

While most of our logic could live in Kotlin, the VS Code extension still needed a TypeScript layer to interact with VS Code's APIs.

The Limits of Kotlin/JS Interop

Ideally, Kotlin Multi-Platform should generate bindings for JavaScript libraries, but TypeScript integration isn't yet available. JetBrains started developing Dukat, a tool for generating Kotlin declarations from TypeScript, but it currently remains stalled and non-functional for non-trivial use cases.

Since this means that the VS Code API is not discoverable in Kotlin MP, using it would require a lot of manual work. Fortunately, TypeScript feels familiar to Kotlin developers, so this wasn't a major hurdle.

Another issue was the blurred line between Node.js and browser targets in Kotlin/JS. This is something we hope will improve with future KMP updates.

Sharing Interface Definitions

One pleasant surprise was how well Kotlin's interface definitions translated to TypeScript. We could define contracts (like RPC messages) in Kotlin and consume them in TypeScript with minimal friction. The reverse—using TypeScript types in Kotlin—was a lot harder, requiring manual type mappings.

The Result

Today, JProfiler's VS Code extension offers the same core features as our other IDE plugins, thanks to Kotlin Multi-Platform. Developers can profile their applications directly from VS Code, with most of the logic shared across all supported platforms.

Along the way, we have learned the following lessons:

  • KMP is powerful but demands adaptation. Replacing JVM APIs requires research and testing.
  • Coroutines simplify cross-platform concurrency, but migrating from threads takes effort.
  • TypeScript interop works best one-way (Kotlin → TypeScript). For now, manual bridging is still necessary.

If you're a Kotlin developer exploring multi-platform possibilities to prevent a full rewrite, our experience shows that even niche use cases like IDE plugins can benefit from KMP.

Connect
Read with RSS
Subscribe by email
Follow on or
Blog Archive