Java Academy · Lesson

Writing a Benchmark

@Benchmark and modes.

Lesson 2 of 413 steps

Writing a Benchmark is a free Java Academy lesson on CoddyKit. This is lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Java Academy learning path, and your progress syncs across the web and the CoddyKit app. The Java Academy course includes 4 lessons in total.

Writing a Benchmark

A JMH benchmark is just a method annotated with @Benchmark inside a regular class. JMH's annotation processor generates the harness around it. This lesson covers the annotations that define what and how you measure.

The @Benchmark Annotation

Mark any public method with @Benchmark and JMH will repeatedly invoke it, timing each call. The method's return value should be returned (not discarded) so JMH can consume it.

A Minimal Benchmark Class

Here is the shape of a benchmark. The import is org.openjdk.jmh.annotations.*. Because JMH is not on this runner's classpath, this is illustrative only.

import org.openjdk.jmh.annotations.Benchmark;

public class StringBench {
    @Benchmark
    public String concat() {
        return "foo" + System.nanoTime();
    }
}

Benchmark Modes

@BenchmarkMode chooses what is reported:

  • Throughput — operations per unit time (higher is better).
  • AverageTime — time per operation (lower is better).
  • SampleTime — distribution of times (percentiles).
  • SingleShotTime — one invocation, good for cold-start.
import org.openjdk.jmh.annotations.*;

public class Bench {
    @Benchmark
    @BenchmarkMode(Mode.AverageTime)
    public int work() {
        return 2 + 2;
    }
}

Choosing Output Units

@OutputTimeUnit sets the time unit for the report so numbers are readable. For a fast operation, nanoseconds make sense; for a slow one, milliseconds.

import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.*;

public class Bench {
    @Benchmark
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.NANOSECONDS)
    public int work() {
        return 42;
    }
}

Returning Results

Always return the result of the work. JMH feeds returned values into a Blackhole automatically, preventing dead-code elimination. A void benchmark that computes and discards is a classic mistake.

Annotation Defaults

Annotations can sit on the method or on the whole class. Class-level annotations apply to every @Benchmark in that class, which keeps configuration DRY when you have several related benchmarks.

Forking

@Fork(n) runs the benchmark in n fresh JVM processes. Forking isolates each run from JIT profiles built up by previous benchmarks, improving reproducibility. The default is one fork.

import org.openjdk.jmh.annotations.*;

@Fork(2)
public class Bench {
    @Benchmark
    public long now() {
        return System.nanoTime();
    }
}

Launching the Runner

You start benchmarks via a main that configures an Options object and a Runner, or via the generated executable JAR. The include(...) regex selects which benchmarks to run.

import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.*;

public class Launcher {
    public static void main(String[] args) throws Exception {
        Options opt = new OptionsBuilder()
            .include("Bench")
            .build();
        new Runner(opt).run();
    }
}

Reading the Output

JMH prints a table with the benchmark name, mode, sample count, the score, the score error, and units, for example: Bench.work avgt 25 3.142 +/- 0.011 ns/op. The +/- is the confidence interval.

Keep Benchmarks Focused

Each benchmark should measure one thing. Mixing setup, I/O, and the operation under test muddies the result. Move preparation into @Setup methods (covered with state) so only the hot path is timed.

Quick Check

Test your understanding of writing benchmarks.

Recap

You learned how to write a JMH benchmark:

  • Annotate a method with @Benchmark and return the result.
  • @BenchmarkMode picks Throughput, AverageTime, SampleTime, or SingleShotTime.
  • @OutputTimeUnit sets readable units; @Fork isolates runs in fresh JVMs.
  • Launch with a Runner + Options or the generated JAR.
  • Keep each benchmark focused on a single hot path.
Free to start

Learn Java with an AI tutor — free

Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.

Courses
104
Lessons
374

Frequently Asked Questions

Is the “Writing a Benchmark” lesson free?

Yes — the full text of “Writing a Benchmark” is free to read here on the web. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Java Academy course, upgrade to CoddyKit PRO. The Java Academy course includes 4 lessons in total.

What will I learn in “Writing a Benchmark”?

@Benchmark and modes. You practise Java Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Java Academy?

No prior experience is required. Java Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 2 of 4.

How long does the “Writing a Benchmark” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Java Academy lesson?

Yes. Every Java Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Why JMH
  2. Writing a Benchmark
  3. Warmup and Iterations
  4. Avoiding Dead-Code Elimination
← Back to Java Academy