# kotlinx-benchmark: benchmarking one Kotlin codebase across four targets

> kotlinx-benchmark is a JetBrains toolkit for measuring multiplatform Kotlin code, covering Kotlin/JVM, Kotlin/JS, Kotlin/Native and the experimental Wasm targets, with statistical analysis and detailed reports. It suits a team whose performance-critical code lives in commonMain and needs numbers on each platform, and its Alpha status is the thing to weigh before depending on it.

**Kotlin/kotlinx-benchmark** — Kotlin multiplatform benchmarking toolkit

- Repository: https://github.com/Kotlin/kotlinx-benchmark
- Stars: 644 · Forks: 44
- Language: Kotlin
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/kotlin-kotlinx-benchmark

## One benchmark definition, four Kotlin targets

kotlinx-benchmark is a toolkit for running benchmarks for multiplatform code written in Kotlin. The README says it is designed to work with Kotlin/JVM, Kotlin/JS, Kotlin/Native, and Kotlin/WasmJs and WasmWasi, with the Wasm targets marked as experimental.

That target list is the reason the project exists. The problem it addresses is not benchmarking in general, it is benchmarking the same code on several platforms without maintaining a separate harness for each one. A library that lives in commonMain, with a platform-specific implementation behind an expect and actual split, is exactly the code where you do not know whether the slow path is on the JVM, in the browser, or on the device. A separate benchmark project per platform means three copies of the measurement code, three sets of numbers that are hard to compare, and a habit of guessing.

The features list is short: low noise and reliable results, statistical analysis, and detailed performance reports. That is the whole product surface, and the three items are what you would choose if you were specifying a benchmarking tool for shared multiplatform code.

The audience is therefore a team whose performance-critical code is genuinely shared, and who would otherwise be measuring platform behaviour by hand. It is not a general microbenchmarking library for a JVM-only service, where the existing tooling is more mature and better documented.

The status is the first thing to notice. The README carries a Kotlin Alpha badge and a JetBrains incubator project badge, which means the API is explicitly not stable. The last push to the repository was on 2026-10-06 and the repository is not archived, so the work is current, but the licence is Apache-2.0 with a LICENSE file at the root.

## The Gradle plugin and the runtime are two separate artifacts

The setup is two pieces that live in different places, and the README walks through them separately.

The plugin is a Gradle plugin with the id org.jetbrains.kotlinx.benchmark. Applied in build.gradle.kts it looks like this:

```kotlin
    // build.gradle.kts
    plugins {
        id("org.jetbrains.kotlinx.benchmark") version "0.5.0"
    }
```

The plugin itself has to be resolvable, which means the Gradle Plugin Portal has to be in the plugin management repositories in settings.gradle.kts:

```kotlin
    // settings.gradle.kts
    pluginManagement {
        repositories {
            gradlePluginPortal()
        }
    }
```

The runtime is a separate library dependency that goes into the common source set, so the benchmark code can see it on every target:

```kotlin
    // build.gradle.kts
    kotlin {
        sourceSets {
            commonMain {
                dependencies {
                    implementation("org.jetbrains.kotlinx:kotlinx-benchmark-runtime:0.5.0")
                }
            }
        }
    }
```

The runtime resolves from Maven Central, so the project repositories block needs mavenCentral() present:

```kotlin
    // build.gradle.kts
    repositories {
        mavenCentral()
    }
```

Both the plugin and the runtime are published, the runtime under org.jetbrains.kotlinx:kotlinx-benchmark-runtime on Maven Central, and the plugin on the Gradle Plugin Portal. The README shows the same four steps in the Groovy DSL as well, with single quotes instead of double, and mentions a separate guide for JVM and Java projects that do not need the multiplatform setup.

## Registering a target is separate from declaring it

The per-target configuration is where the multiplatform specifics live, and the first JVM example shows the pattern the other targets follow.

A JVM target is declared in the kotlin block:

```kotlin
    // build.gradle.kts
    kotlin {
        jvm()
    }
```

and then registered as a benchmark target:

```kotlin
    // build.gradle.kts
    benchmark {
        targets { 
            register("jvm")
        }
    }
```

The README is explicit that to run benchmarks on a platform your project has to target that platform, and that different platforms may need distinct requirements and settings. That is a real source of friction in a multiplatform project, since declaring the target in the kotlin block and registering it in the benchmark block are two separate edits, and forgetting the second one produces a project that compiles for a platform and benchmarks nothing on it.

The JVM case then has one extra step that the other targets do not, and it is a Kotlin-specific one. The allopen plugin has to be applied so that benchmark classes and methods are open, and the configuration names the JMH state annotation:

```kotlin
    // build.gradle.kts
    plugins {
        kotlin("plugin.allopen") version "2.2.0"
    }

    allOpen {
        annotation("org.openjdk.jmh.annotations.State")
    }
```

The reason is in the README's own explanation further down. In Kotlin, classes are final by default, which means they cannot be overridden, and that conflicts with the Java Microbenchmark Harness, where the generated subclass is what carries the state. So the JMH annotation on the class has to be turned into an all-open annotation for the plugin to act on it.

## Writing a benchmark: one annotation and a state scope

The README shows the shape of a benchmark class, and it is recognisably JMH's model with Kotlin syntax.

```kotlin
    // MyBenchmark.kt
    @State(Scope.Benchmark)
    class MyBenchmark {
        // Benchmarking-related methods and variables
        @Benchmark
        fun benchmarkMethod() {
            // benchmarking logic
        }
    }
```

Two annotations carry the meaning. The class is annotated with the JMH state annotation at benchmark scope, which means one instance of the class is shared across all the iterations of a run, so any fields on it are setup cost rather than measured work. The method carries the benchmark annotation, and the method body is the code being measured.

The state scope choice is the one decision that affects the numbers, and it is worth being deliberate about it. The scope determines whether the object is created once per run, once per iteration, or once per thread, and putting setup work in the wrong scope charges it to the measurement. The README example uses the benchmark scope, which is the right choice when the setup is genuinely expensive and reusable.

The tooling around this is documented separately. The guides list covers writing benchmarks, configuring benchmark execution, setting up Kotlin/JVM and Java projects for benchmarking, setting up a separate source set for benchmarks, and an overview of the tasks the Gradle plugin provides. The separate source set guide is the one to read if you do not want benchmark code in your production source sets, which is a common requirement in a shared multiplatform library where the benchmark code would otherwise ship in the artefact.

## Configuration profiles and the analysis step

Two parts of the README's table of contents point at what separates this from a home-grown timing loop.

The first is benchmark configuration profiles, documented on its own page for configuring benchmark execution. The second is analyzing results, which has its own section and a set of example notebooks.

The examples directory makes the analysis story concrete. There is a simple-benchmark-analysis notebook, a compare-benchmark-runs notebook and a compare-hypothesis notebook, alongside example projects for Java, Kotlin/JVM, Kotlin multiplatform, a JVM project comparing hypotheses, a JVM project with a separate benchmark source set, and a build file. Notebooks as the analysis format is a deliberate choice: the output of a benchmark run is a set of distributions, and the question you actually want to ask is usually whether the new distribution differs from the old one, which is a statistical question rather than a lookup.

The features list names statistical analysis and detailed performance reports as separate capabilities, which is a reasonable way to describe a tool that does both distribution analysis and export of the raw data.

The Kotlin/JVM entry in the examples, kotlin-jvm-compare-hypothesis, is the one to look at first if you want to know what comparing runs looks like in practice, since the hypothesis-comparison framing is the part that is hardest to reconstruct on your own. The feature list also claims low noise and reliable results, which for a benchmarking tool is the claim that matters most, and the one you should check for yourself against your own workload rather than accept from a README.

## Where kotlinx-benchmark is the wrong tool

The first wrong-tool case is a JVM-only project. The README itself points you to a separate guide dedicated to setting up benchmarking in Kotlin/JVM and Java projects specifically, and a dedicated JVM harness gives you the JMH model without a Gradle plugin, a multiplatform configuration and an experimental Wasm target to think about. The multiplatform setup is the value; without it, you are carrying configuration cost for a feature you do not use.

The second is a project that needs a stable API. Alpha is not a polite disclaimer, it means the surface can change under you, and a benchmark suite is infrastructure that other people depend on. If your build depends on the plugin id and the runtime artifact, an Alpha release means you may be updating your build configuration as part of a Kotlin upgrade.

The third is the Wasm path specifically. The README states that because the Kotlin/WasmJs target is experimental and in active development, it guarantees support only for the specific Kotlin version used to build the library, and for the latest version that is Kotlin 2.2.0. A project betting on Wasm output is betting on an experimental target with a version-locked toolkit.

The fourth is microbenchmarking at the JVM level for cases where you already have a mature tool. The JMH-based model here is inherited for the JVM path, and if your questions are about JVM-level details, the existing tooling answers them without a plugin.

The alternative to weigh is writing the measurement loop yourself, and the difference in approach is what the examples are for. A home-grown loop measures a function once and prints a number. This toolkit runs repeated iterations with a state model, applies statistical analysis to the resulting distributions, and produces reports you can compare across runs and across targets. The trade is that you accept a Gradle plugin, a runtime dependency, an Alpha status and the requirement to keep your Kotlin version in step with the library.

## Conclusion

kotlinx-benchmark fits a team with performance-sensitive code in a shared common source set that needs measurements per target, since the same benchmark definition runs on JVM, JS, Native and Wasm. It is the wrong choice for a project that needs a stable API, because the project is marked Alpha, and the wrong choice if you only need JVM numbers, where a dedicated JVM harness does the job with less ceremony. Before adopting it, confirm your Kotlin version and Gradle version meet the stated requirements, read the configuration options guide for the noise settings rather than trusting defaults, and remember that the Wasm targets are only guaranteed for the exact Kotlin version the library was built with.

## FAQ

### What is kotlinx-benchmark and which targets does it support?

It is a Kotlin multiplatform benchmarking toolkit designed to work with Kotlin/JVM, Kotlin/JS, Kotlin/Native, and Kotlin/WasmJs and WasmWasi, with the Wasm targets marked as experimental.

### How do I add the kotlinx-benchmark plugin to a Gradle project?

Apply the plugin with the id org.jetbrains.kotlinx.benchmark, add the Gradle Plugin Portal to the pluginManagement repositories, add the kotlinx-benchmark-runtime dependency to the commonMain source set, and make sure mavenCentral() is in the project repositories.

### Why does kotlinx-benchmark need the allopen plugin for Kotlin/JVM?

The README explains that in Kotlin classes are final by default, which conflicts with the Java Microbenchmark Harness, so the allopen plugin is applied with the JMH State annotation to make the benchmark classes and methods open.

### What Kotlin and Gradle versions does kotlinx-benchmark require?

The README says to use Kotlin 2.2.0 or newer and the latest stable Gradle 8 release or newer, and notes that the WasmJs target only guarantees support for the specific Kotlin version used to build the library.

## Sources

- [Issues](https://github.com/Kotlin/kotlinx-benchmark/issues)
- [Kotlin/kotlinx-benchmark on GitHub](https://github.com/Kotlin/kotlinx-benchmark)
- [License: Apache-2.0](https://github.com/Kotlin/kotlinx-benchmark/blob/master/LICENSE)
- [README](https://github.com/Kotlin/kotlinx-benchmark/blob/master/README.md)
- [Releases](https://github.com/Kotlin/kotlinx-benchmark/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kotlin-kotlinx-benchmark
