> ## Documentation Index
> Fetch the complete documentation index at: https://codspeed.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Writing Benchmarks in Java

> Create benchmarks for your Java codebase using Java Microbenchmark Harness

export const CIWorkflow = ({minimal = false, enableWorkflowDispatch = true, runsOn = "ubuntu-latest", highlight = [], mode, modes, submodules = false, preSteps = [], buildSteps = ["# ...", "# Setup your environment here:", "#  - Configure your Python/Rust/Node version", "#  - Install your dependencies", "#  - Build your benchmarks (if using a compiled language)", "# ..."], benchmarkCommand = ["<Insert your benchmark command here>"], jobName = "Run benchmarks", env = {}}) => {
  const modeList = modes || (mode ? [mode] : undefined);
  if (!modeList || modeList.length === 0) {
    throw new Error("mode or modes is required");
  }
  const indent = (lines, depth) => {
    const reindentedLines = lines.map(l => l.length === 0 ? l : (" ").repeat(depth) + l);
    return reindentedLines.join("\n");
  };
  const workflowDispatchSection = enableWorkflowDispatch ? "  # `workflow_dispatch` allows CodSpeed to trigger backtest\n" + "  # performance analysis in order to generate initial data.\n" + "  workflow_dispatch:\n" : "";
  let yaml = "";
  if (!minimal) {
    yaml += `
name: CodSpeed Benchmarks

on:
  push:
    branches:
      - "main" # or "master"
  pull_request:
`;
    yaml += workflowDispatchSection;
  }
  yaml += `
jobs:
  benchmarks:
    name: ${jobName}
    runs-on: ${runsOn}`;
  if (!minimal) {
    yaml += `
    permissions: # optional for public repositories
      contents: read # required for actions/checkout
      id-token: write # required for OIDC authentication with CodSpeed`;
  }
  if (preSteps.length > 0) yaml += "\n" + indent(preSteps, 4);
  yaml += `
    steps:
      - uses: actions/checkout@v5`;
  if (submodules) {
    const value = typeof submodules === "string" ? submodules : "true";
    yaml += `\n        with:\n          submodules: ${value}`;
  }
  yaml += "\n" + indent(buildSteps, 6);
  const modeValue = modeList.join(",");
  yaml += `
      - name: Run the benchmarks
        uses: CodSpeedHQ/action@v4
        with:
          mode: ${modeValue}`;
  if (benchmarkCommand.length > 0) {
    const indentedBenchCommand = benchmarkCommand.length > 1 ? benchmarkCommand[0] + "\n" + indent(benchmarkCommand.slice(1), 12) : benchmarkCommand;
    const runLine = indent(["run: "], 10) + indentedBenchCommand;
    yaml += `\n${runLine}`;
  }
  const envEntries = Object.entries(env);
  if (envEntries.length > 0) {
    const envLines = ["env:", ...envEntries.map(([k, v]) => `  ${k}: ${v}`)];
    yaml += "\n" + indent(envLines, 8);
  }
  return <CodeBlock language="yaml" highlight={JSON.stringify(highlight)} {...minimal || ({
    filename: ".github/workflows/codspeed.yml",
    icon: "github"
  })}>
      {yaml}
    </CodeBlock>;
};

<Note>
  The Java integration is still in early development, and **only the [walltime
  instrument](/instruments/walltime) is currently supported**.

  If you have any feedback, please reach out to us via
  [Discord](https://discord.gg/MxpaCfKSqF) or
  [email our support](mailto:contact@codspeed.io).
</Note>

Integrating CodSpeed into your Java project works through a fork of
[JMH](https://github.com/openjdk/jmh) (Java Microbenchmark Harness). You write
standard JMH benchmarks and swap in the CodSpeed JMH fork as a dependency. When
running in CI with CodSpeed, the results are automatically collected and
reported.

## Installation

CodSpeed provides a fork of JMH that collects walltime results and sends them to
CodSpeed.

<Tabs>
  <Tab title="Gradle">
    Add the CodSpeed JMH fork as a Git submodule:

    ```bash theme={null}
    git submodule add https://github.com/CodSpeedHQ/codspeed-jvm.git third-party/codspeed-jvm
    ```

    Then include it as a composite build in your `settings.gradle.kts`, with
    dependency substitution to redirect JMH dependencies to the CodSpeed fork:

    ```kotlin settings.gradle.kts theme={null}
    includeBuild("third-party/codspeed-jvm/jmh-fork") {              // [!code ++]
        dependencySubstitution {                          // [!code ++]
            substitute(module("org.openjdk.jmh:jmh-core"))           // [!code ++]
                .using(project(":jmh-core"))              // [!code ++]
            substitute(module("org.openjdk.jmh:jmh-generator-annprocess")) // [!code ++]
                .using(project(":jmh-generator-annprocess")) // [!code ++]
        }                                                 // [!code ++]
    }                                                     // [!code ++]
    ```

    We recommend using the
    [JMH Gradle Plugin](https://github.com/melix/jmh-gradle-plugin) as it handles
    benchmark compilation and provides the `jmh` task. Add it to your
    `build.gradle.kts`:

    ```kotlin build.gradle.kts theme={null}
    plugins {
        java
        id("me.champeau.jmh") version "0.7.2"// [!code ++]
    }
    ```
  </Tab>

  <Tab title="Maven">
    Add the CodSpeed JMH fork as a Git submodule and publish it to your local Maven
    repository:

    ```bash theme={null}
    git submodule add https://github.com/CodSpeedHQ/codspeed-jvm.git third-party/codspeed-jvm
    cd third-party/codspeed-jvm
    ./gradlew -p jmh-fork publishToMavenLocal
    ```

    Then replace the JMH dependencies in your `pom.xml` with the CodSpeed fork.
    Maven resolves from `~/.m2/repository` by default. The repository entry below is
    only necessary if your local repository is at a non-default location:

    ```xml pom.xml theme={null}
    <repositories>                                        // [!code ++]
        <repository>                                      // [!code ++]
            <id>local</id>                                // [!code ++]
            <url>file://${user.home}/.m2/repository</url> // [!code ++]
        </repository>                                     // [!code ++]
    </repositories>                                       // [!code ++]

    <dependencies>
        <dependency>
            <groupId>org.openjdk.jmh</groupId>// [!code --]
            <version>1.37</version>// [!code --]
            <groupId>io.codspeed.jmh</groupId>// [!code ++]
            <version>0.1.0</version>// [!code ++]
            <artifactId>jmh-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.openjdk.jmh</groupId>// [!code --]
            <version>1.37</version>// [!code --]
            <groupId>io.codspeed.jmh</groupId>// [!code ++]
            <version>0.1.0</version>// [!code ++]
            <artifactId>jmh-generator-annprocess</artifactId>
        </dependency>
    </dependencies>
    ```

    <Note>
      Your Maven project must be configured to produce an executable benchmark JAR.
      See the [official JMH setup
      guide](https://github.com/openjdk/jmh?tab=readme-ov-file#preferred-usage-command-line)
      for instructions.
    </Note>

    We're planning to publish to Maven Central in the future. If you need this,
    please reach out via [Discord](https://discord.gg/MxpaCfKSqF) or
    [email](mailto:contact@codspeed.io).
  </Tab>
</Tabs>

## Creating benchmarks

Write your benchmarks using standard JMH annotations:

```java FibBenchmark.java theme={null}
package bench;

import org.openjdk.jmh.annotations.*;

public class FibBenchmark {

  @Benchmark
  public long fib() {
    return fib(30);
  }

  private static long fib(int n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);
  }
}
```

<Info>
  JMH benchmarks must return their result or use `Blackhole.consume()` to
  prevent the JVM from eliminating dead code. All examples on this page return
  the computed value.
</Info>

<Tip>
  For an in-depth tutorial on JMH, see the [How to Benchmark Java with
  JMH](/guides/how-to-benchmark-java-with-jmh) guide.
</Tip>

## Testing the benchmarks locally

To run the benchmarks with CodSpeed locally, first install the `codspeed`
runner:

```bash theme={null}
curl -fsSL https://codspeed.io/install.sh | sh
```

Then run your benchmarks with CodSpeed:

<Tabs>
  <Tab title="Gradle">
    ```shellsession title=terminal icon="square-terminal" theme={null}
    $ codspeed run --mode walltime -- ./gradlew jmh
    ►►► Running the benchmarks
    # JMH version: 0.1.0
    # VM version: JDK 21.0.6, OpenJDK 64-Bit Server VM, 21.0.6+7
    # Warmup: 5 iterations, 10 s each
    # Measurement: 5 iterations, 10 s each
    # Benchmark: bench.FibBenchmark.fib

    # Run progress: 0.00% complete, ETA 00:01:40
    # Warmup Iteration   1: 112.541 ns/op
    ...

    Benchmark              Mode  Cnt      Score      Error  Units
    FibBenchmark.fib       avgt    5    110.342 ±    2.153  ns/op
    ```
  </Tab>

  <Tab title="Maven">
    ```shellsession title=terminal icon="square-terminal" theme={null}
    $ mvn package -q
    $ codspeed run --mode walltime -- java -jar target/benchmarks.jar
    ►►► Running the benchmarks
    # JMH version: 0.1.0
    # VM version: JDK 21.0.6, OpenJDK 64-Bit Server VM, 21.0.6+7
    # Warmup: 5 iterations, 10 s each
    # Measurement: 5 iterations, 10 s each
    # Benchmark: bench.FibBenchmark.fib

    # Run progress: 0.00% complete, ETA 00:01:40
    # Warmup Iteration   1: 112.541 ns/op
    ...

    Benchmark              Mode  Cnt      Score      Error  Units
    FibBenchmark.fib       avgt    5    110.342 ±    2.153  ns/op
    ```
  </Tab>
</Tabs>

## Running the benchmarks in your CI

To generate performance reports, you need to run the benchmarks in your CI. This
allows CodSpeed to automatically run benchmarks and warn you about regressions
during development.

<Tip>
  If you want more details on how to configure the CodSpeed action, you can check
  out the [Continuous Reporting section](/integrations/ci).
</Tip>

Here is an example of a GitHub Actions workflow that runs the benchmarks and
reports the results to CodSpeed on every push to the `main` branch and every
pull request:

<Tabs>
  <Tab title="Gradle">
    <CIWorkflow
      mode="walltime"
      runsOn="codspeed-macro"
      submodules="recursive"
      buildSteps={[
    "- uses: actions/setup-java@v4",
    "  with:",
    "    distribution: temurin",
    "    java-version: 21",
  ]}
      benchmarkCommand={["./gradlew jmh"]}
    />
  </Tab>

  <Tab title="Maven">
    <CIWorkflow
      mode="walltime"
      runsOn="codspeed-macro"
      submodules="recursive"
      buildSteps={[
    "- uses: actions/setup-java@v4",
    "  with:",
    "    distribution: temurin",
    "    java-version: 21",
    "- name: Install CodSpeed JMH fork",
    "  run: cd third-party/codspeed-jvm && ./gradlew -p jmh-fork publishToMavenLocal",
    "- name: Build benchmark JAR",
    "  run: mvn package -q",
  ]}
      benchmarkCommand={["java -jar target/benchmarks.jar"]}
    />
  </Tab>
</Tabs>

## Advanced usage

JMH provides many features for writing expressive benchmarks. Below is a
selection that can be useful in CodSpeed benchmarks.

### Parameterized benchmarks

Use `@Param` to run the same benchmark with different input values:

```java ParamBenchmark.java theme={null}
package bench;

import org.openjdk.jmh.annotations.*;

@State(Scope.Benchmark)
public class ParamBenchmark {

  @Param({"10", "20", "30"})
  int n;

  @Benchmark
  public long fib() {
    return fib(n);
  }

  private static long fib(int n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);
  }
}
```

### Shared state

Use `@State` to share setup logic across benchmarks and control the scope of the
state object:

```java StateBenchmark.java theme={null}
package bench;

import org.openjdk.jmh.annotations.*;
import java.util.ArrayList;
import java.util.List;

@State(Scope.Benchmark)
public class StateBenchmark {

  List<Integer> list;

  @Setup
  public void setup() {
    list = new ArrayList<>();
    for (int i = 0; i < 1000; i++) {
      list.add(i);
    }
  }

  @Benchmark
  public int sum() {
    return list.stream().mapToInt(Integer::intValue).sum();
  }
}
```

For a deeper dive into JMH, see the dedicated guide:

<Card title="How to Benchmark Java with JMH" icon="java" href="/guides/how-to-benchmark-java-with-jmh">
  An in-depth guide to writing JMH benchmarks: project setup, annotations,
  parameters, common pitfalls, and CodSpeed CI integration.
</Card>

## Compatibility

* **JDK 17 or later** is required.
* All standard JMH annotations are supported.
* Only the [Walltime instrument](/instruments/walltime) is supported
* CodSpeed uses a custom mode for all benchmarks to collect statistically
  significant results. Any `@BenchmarkMode` annotations in your code are
  ignored.

If you run into issues or require certain features, please
[open an issue](https://github.com/CodSpeedHQ/codspeed-jvm/issues) or
[join our Discord](https://discord.com/invite/MxpaCfKSqF) to get help.

## Next steps

<CardGroup>
  <Card title="Example repository" icon="github" href="https://github.com/CodSpeedHQ/codspeed-jvm">
    The CodSpeed JVM repository with example JMH benchmarks.
  </Card>

  <Card title="How to Benchmark Java with JMH" icon="java" href="/guides/how-to-benchmark-java-with-jmh">
    An in-depth guide to writing JMH benchmarks with CodSpeed.
  </Card>

  <Card title="Walltime instrument" icon="stopwatch" href="/instruments/walltime">
    Learn more about the Walltime instrument and how to use it.
  </Card>

  <Card title="Dive into performance changes" icon="bars-sort" href="/features/profiling">
    Learn more about profiling and how to read flame graphs.
  </Card>
</CardGroup>
