Key Takeaways
A benchmark result is only useful if another engineer can inspect the source package, rerun the command path, and review the generated artifacts. Reproducibility comes before interpretation.
This package treats the Go
net/httpservice and the Java Helidon SE service symmetrically at the harness level. Both are built from source through package scripts, validated against the same public contract, and expected to write evidence under the same artifact tree.The run manifest is the main handoff artifact for this package. It connects toolchain versions, service configuration, validation status, matrix configuration, and generated file locations so later analysis can trace supported claims back to the run artifacts that support them.
This article covers benchmark execution and reproduction. It does not compare latency, throughput, resource use, language superiority, or Oracle AI Database performance.
Service-runtime choices matter when an HTTP endpoint sits in front of a model call, retrieval step, vector search request, enrichment pipeline, or another application workload. Teams naturally ask whether Go, Java, framework choice, JDK choice, warmup, or runtime configuration will change the shape of that service under load.
Before any performance chart deserves attention, though, the artifact behind it has to be runnable.
That is the job of this article.
In this part of the single-machine benchmark series, we move from design to execution. Earlier articles defined the benchmark goals, service contract, Go implementation shape, Java Helidon SE implementation shape, load-generator design, and run methodology. Here we start from the benchmark source package, run the prerequisite and validation gates, build both services, run the benchmark matrix, and inspect the artifacts that later articles will analyze.
This is not the results article. It is the reproduction article.
This reproduction path is scoped to a local Linux environment using Bash and the package scripts. Other environments, including macOS, Windows, WSL, containers, and CI runners, may work only where the package explicitly documents and validates them.
Version basis: the command path and sample output reflect the package layout used for this article, so script wording, manifest field names, artifact names, and dependency versions can vary by package release.
What this article helps you reproduce
The benchmark source package is the unit of trust. Not a chart. Not a slide. Not an isolated binary. The package includes the Go service source, the Java Helidon SE service source, shared contracts, fixtures, scripts, package-local tooling, manifest templates, and artifact directories. The reproduction run writes generated evidence under artifacts/.
In this walkthrough, we use the package-root command path to show:
- Where the source files, contracts, fixtures, and scripts live.
- How to verify the local Linux/Bash environment before building anything.
- How to capture toolchain and host facts.
- How to validate the shared service contract.
- How to build both services from source.
- How to run smoke checks on loopback.
- How to execute the benchmark matrix without publishing benchmark numbers.
- How to inspect the run manifest and generated artifacts.
The package root is the top-level directory of the materialized benchmark source package. In this package, it is the directory containing scripts/, contracts/, fixtures/, services/, tools/, manifests/, and artifacts/.
Run every command in this article from the package root unless a section says otherwise. The validation scripts load contract files relative to that root, and the benchmark scripts write generated evidence into the package’s artifacts/ tree.
If a command is run from the wrong directory, the scripts fail fast rather than silently loading a different contract or writing artifacts somewhere unexpected. That is deliberate. The working directory is part of the reproduction contract.

We reproduce the benchmark by moving from the source package through prerequisite checks, environment capture, validation, builds, smoke checks, matrix execution, artifact capture, and manifest review before interpreting any results.
What this article does not claim
This article does not compare Go and Java latency. It does not compare throughput. It does not compare CPU use, memory use, garbage collection behavior, JDK distributions, or deployment cost. It does not say one language is better than the other.
It also does not measure Oracle AI Database performance. This package validates the benchmark execution harness. If another benchmark slice adds an Oracle AI Database dependency, that slice needs its own driver configuration, credential handling, manifest fields, validation gates, and scope statement.
For this article, a good run is not a fast run. A good run is one where the package builds, validates, executes, and captures evidence without skipping a gate.
Source package layout
Start by orienting yourself in the package. The first command is not a build. It is a quick check that you are standing in the right place.
pwdfind . -maxdepth 2 -type d | sort
The top-level shape should look like this:
../artifacts./artifacts/benchmark./artifacts/env./artifacts/logs./artifacts/manifests./artifacts/validation./contracts./docs./fixtures./fixtures/errors./fixtures/valid./manifests./scripts./services./services/go-http./services/java-helidon./tools./tools/benchctl
The Go implementation is intended to use Go’s standard-library net/http package, with its service entry point under services/go-http/cmd/server/.
The Java service lives under services/java-helidon/. It is a Helidon SE service, with its Helidon version pinned in services/java-helidon/pom.xml. The Java build packages the service JAR and runs it with the classpath resolved by the package’s Maven build.
The shared service contract lives at contracts/work-contract.json. The valid and error fixtures live under fixtures/valid/ and fixtures/errors/. The package-local benchmark and contract tooling lives under tools/benchctl/, so the orchestration path is inspectable as source.
Generated evidence goes under artifacts/. That includes environment facts, validation output, service logs, benchmark matrix output, and manifests.
The split matters. Source inputs and generated evidence need to be easy to separate. Later analysis should be able to connect a supported claim to a manifest, validation output, generated file, or log instead of relying on memory or console scrollback.
Step 1: Verify prerequisites
Run the prerequisite check before you build or launch either service.
./scripts/check-prereqs.sh
A successful run should include these categories of checks and end with a PASS result. Exact log wording can vary by package version:
[check-prereqs] package root: PASS[check-prereqs] Go toolchain: PASS[check-prereqs] Java runtime: PASS[check-prereqs] Java compiler: PASS[check-prereqs] Maven command: PASS[check-prereqs] required scripts: PASS[check-prereqs] contracts directory: PASS[check-prereqs] artifacts writable: PASS[check-prereqs] result: PASS
This step checks that the expected commands and paths are visible from the package root. It looks for tools such as Bash, Go, java, javac, Maven, curl, and common Linux utilities used by the scripts. It also verifies required package paths such as contracts/work-contract.json, fixtures/, services/go-http/, services/java-helidon/, and tools/benchctl/.
The prerequisite check is a preflight gate, not a full build. If the package does not vendor all dependencies, the Java build needs access to the configured Maven repositories or a pre-populated local Maven cache, and the Go build needs access to any required Go modules or a pre-populated Go module cache. Dependency-resolution problems usually become visible at the later build gates.
A passing prerequisite check means the package can start the reproduction path. It does not mean the benchmark is valid, and it does not mean the Java and Go services are behaviorally comparable. Those claims require later gates.
Helidon 4 requires Java 21 or later. The prerequisite check records the Java commands it finds; the Java build and runtime validation gates still have to pass.
Step 2: Capture environment facts
Benchmarks become hard to trust when the environment is described from memory. The package records environment facts before the benchmark matrix runs.
./scripts/capture-env.sh
A successful run writes environment and manifest files and ends with a PASS result. The output should include files in these categories:
[capture-env] wrote artifacts/env/go-version.txt[capture-env] wrote artifacts/env/java-version.txt[capture-env] wrote artifacts/env/javac-version.txt[capture-env] wrote artifacts/env/os-release.txt[capture-env] wrote artifacts/manifests/environment-manifest.json[capture-env] result: PASS
Now inspect the files that were created:
find artifacts -maxdepth 3 -type f | sort
At this point you should see environment files like these:
artifacts/env/architecture.txtartifacts/env/cpu.txtartifacts/env/go-version.txtartifacts/env/java-version.txtartifacts/env/javac-version.txtartifacts/env/kernel.txtartifacts/env/maven-version.txtartifacts/env/memory.txtartifacts/env/os-release.txtartifacts/manifests/environment-manifest.json
The environment manifest records toolchain and host facts used by a specific benchmark execution. It does not try to be a complete machine inventory. It records the facts that matter to reproduction: Go version, Java runtime version, Java compiler version, Maven version, OS and kernel details, architecture, CPU summary, memory summary, package version, and the package-local tool version when that tool has already been built.
Record the exact JDK distribution, version, and build string from java -version, not only the Java feature version. JDK distribution and build details are benchmark inputs, just like the Go version, framework configuration, warmup settings, and JVM arguments supplied through JAVA_SERVICE_JVM_ARGS.
That is enough for the next engineer to ask useful questions. Did the run use a different JDK? Was the Go version different? Did the architecture change? Were JVM arguments supplied? Those are not trivia. They are benchmark inputs.
Environment capture explains where the benchmark ran. It does not make a single-machine run representative of every deployment shape.
Step 3: Validate the shared contract
Before we build confidence in any measurement, both services have to satisfy the same behavioral contract.
The contract defines GET /health, HEAD /health, and POST /work. The /work endpoint accepts application/json with four required fields: requestId, payloadSize, seed, and extraWork. Validation is intentionally strict. The decoded request body is limited to 4096 bytes, requestId must be a string with length 1..128, payloadSize must be an integer in the range 0..131072, seed must be an integer in the range 0..2147483647, and extraWork must be an integer in the range 0..100.
Successful /work responses return requestId, payloadSize, extraWork, checksum, and ok. The validators also reject unknown fields, duplicate fields, missing required fields, scalar type violations, quoted numeric strings, numeric bound violations, invalid JSON, multiple JSON documents, and decoded bodies larger than 4096 bytes.
Run the contract wrapper from the package root:
./scripts/validate-contract.sh
A successful run should show the Go service checks, the Java Helidon SE service checks, and the equivalence cases all passing:
[validate-contract] loaded shared contract fixtures[validate-contract] Go service contract checks: PASS[validate-contract] Java Helidon SE service contract checks: PASS[validate-contract] error response equivalence: PASS[equivalence] case work-small: PASS[equivalence] case work-medium: PASS[equivalence] case work-maximum: PASS[equivalence] case missing-request-id: PASS[equivalence] case payload-size-too-large: PASS[equivalence] case duplicate-field: PASS[equivalence] comparing success responses: PASS[equivalence] comparing error responses: PASS[equivalence] result: PASS[validate-contract] complete shared validation: PASS
This wrapper does more than inspect source files. It runs the package’s unit and tool checks, builds the services as needed, starts both services on loopback, and uses the package-local contracttest command to compare responses across the shared fixtures.
That is the important part: equivalence is tested against the package contract, not assumed.
The Go and Java implementations are allowed to be different internally. They use different languages, different runtime models, and different libraries. But before the benchmark matrix runs, they need to agree on the external behavior defined by the contract.
Because this validation starts live services on loopback, make sure the package’s validation ports are available before running it. If validation fails with a bind or connection error, stop any stale service process first, then rerun the validation command. Use alternate port variables only where the package scripts explicitly document support for them.
If this step fails, stop. Fix the implementation, fixture, or environment problem first. A downstream benchmark run after a failed contract gate is not comparable evidence.
Step 4: Build the Go and Java services
Now build both benchmark service implementations from the package source.
Start with Go:
./scripts/build-go.sh
A successful Go build runs the configured checks and writes the service binary:
[build-go] checking module[build-go] building service[build-go] wrote bin/go-http-server[build-go] result: PASS
The point is not that Go can produce a binary. The point is that this package does not ask you to trust a prebuilt benchmark service. The service used by the benchmark is built through the same package-root path every time.
Now build the Java Helidon SE service:
./scripts/build-java.sh
A successful Java build resolves dependencies, compiles the service, runs tests, and packages the JAR:
[build-java] resolving Java build[validate-java] resolving Java build[validate-java] compiling main sources[validate-java] compiling tests[validate-java] running tests[validate-java] packaging Java Helidon SE service[validate-java] wrote services/java-helidon/target/java-helidon-benchmark.jar[validate-java] result: PASS[build-java] wrote services/java-helidon/target/java-helidon-benchmark.jar[build-java] result: PASS
The Java build resolves the Helidon dependencies, compiles the service, compiles and runs the Java tests, and packages services/java-helidon/target/java-helidon-benchmark.jar.
One naming detail matters here: this is the Java Helidon SE implementation used in this benchmark package. It is not a claim about every Java framework or every JVM configuration. A Spring Boot service, Quarkus service, Micronaut service, GraalVM Native Image build, or a different Helidon configuration would be a different benchmark artifact.
This article’s command path runs the Java Helidon SE service on the configured JDK. It does not invoke a GraalVM Native Image build. If a package version adds Native Image support, that should be treated as a separate benchmark artifact and recorded separately.
Step 5: Start the services and run smoke checks
Build success is not enough. We also want a fast operational check that both services can start, bind to loopback, answer /health, answer a basic /work request, write logs, and stop cleanly.
Run the smoke script:
./scripts/run-smoke.sh
By default, the script uses 127.0.0.1:18180 for the Go service and 127.0.0.1:18181 for the Java Helidon SE service. You can override those with GO_SERVICE_PORT and JAVA_SERVICE_PORT if your local machine already has something on those ports.
A successful smoke run should show both services becoming ready, passing a request check, and stopping cleanly:
[smoke-go] ready: http://127.0.0.1:18180/health[smoke-go] request: PASS[smoke-java] ready: http://127.0.0.1:18181/health[smoke-java] request: PASS[smoke-go] stopped[smoke-java] stopped[smoke] result: PASS
If you need alternate ports for the smoke check, pass them explicitly:
GO_SERVICE_PORT=19180 JAVA_SERVICE_PORT=19181 ./scripts/run-smoke.sh
Smoke checks are not load tests. They are a safety gate before the benchmark matrix runs.
The logs from this step are written under artifacts/logs/, and the smoke validation file is written under artifacts/validation/smoke-validation.json. Those files matter because a later failed artifact verification should tell you whether the problem happened before, during, or after service launch.
Step 6: Run the benchmark matrix
The benchmark matrix is the configured set of benchmark executions for this package. It defines which service runs, which workload settings apply, and which artifacts the runner should capture.
Now run the matrix command, but keep the scope clear: this article still does not publish benchmark results.
./scripts/run-matrix.sh
A successful matrix run should show the matrix loading, each service being prepared and captured, the manifest being generated, and the final result passing:
[run-matrix] loaded benchmark matrix[run-matrix] prepared Go service run[run-matrix] captured Go service artifacts[run-matrix] prepared Java Helidon SE service run[run-matrix] captured Java Helidon SE service artifacts[generate-manifest] wrote artifacts/manifests/run-manifest.json[generate-manifest] result: PASS[run-matrix] wrote run manifest[run-matrix] result: PASS
The default package matrix is intentionally small. Its job in this article is to prove orchestration and artifact capture: start one service, run the package-local benchctl tool against it, capture the output, stop it, repeat symmetrically for the other service, and write a run manifest.
benchctl is package-local orchestration tooling for this demo package. It is not being presented here as a general-purpose load-testing standard or as a substitute for a production benchmark methodology. This article does not claim that benchctl avoids coordinated omission or is suitable for production-grade latency analysis; those properties would need to be documented and validated before using its output for latency conclusions.
This local matrix is useful for proving orchestration and artifact capture. It is not evidence that the same behavior will appear on another host, container runtime, kernel configuration, cloud shape, or production deployment.
That design keeps this article focused. The matrix step produces data for later analysis. Here we prove that the run can be executed and captured; we do not interpret the generated files as performance results.
If you need the matrix run to use nondefault service ports, pass the port variables to the matrix command as well:
GO_SERVICE_PORT=19180 JAVA_SERVICE_PORT=19181 ./scripts/run-matrix.sh
A partial matrix run is not comparable benchmark evidence. If the Go side completes and the Java side fails, or if the matrix runs but the manifest is missing, you do not have a valid run. You have a failed run with useful diagnostic artifacts.
Step 7: Inspect generated artifacts
After the matrix runs, verify the expected artifact set:
./scripts/verify-artifacts.sh
The verifier should report each required artifact category as present:
[verify-artifacts] environment manifest: PASS[verify-artifacts] run manifest: PASS[verify-artifacts] contract validation output: PASS[verify-artifacts] Go build output: PASS[verify-artifacts] Java build output: PASS[verify-artifacts] smoke-test logs: PASS[verify-artifacts] benchmark matrix artifacts: PASS[verify-artifacts] result: PASS
Now list the generated files:
find artifacts -maxdepth 3 -type f | sort
After a successful run, the artifact tree should include files like these, with the generated run manifest acting as the source of truth for the exact artifact set:
artifacts/benchmark/go/go-summary.jsonartifacts/benchmark/java/javaHelidon-summary.jsonartifacts/env/go-version.txtartifacts/env/java-version.txtartifacts/env/javac-version.txtartifacts/logs/go-build-output.txtartifacts/logs/go-service.logartifacts/logs/java-build-output.txtartifacts/logs/java-service.logartifacts/manifests/environment-manifest.jsonartifacts/manifests/run-manifest.jsonartifacts/validation/contract-validation.jsonartifacts/validation/equivalence-validation.jsonartifacts/validation/smoke-validation.json

The package separates source inputs from generated evidence so later analysis can trace supported claims back to contracts, scripts, validation output, logs, benchmark files, and manifests.
The first artifact to inspect is the run manifest.
cat artifacts/manifests/run-manifest.json
The run manifest is the benchmark execution record. It is intended to connect the package version, service configuration, toolchain facts, matrix configuration, and artifact locations for a specific run.
The exact manifest fields are defined by the package release you run. For this package, the generated manifest should describe the run identity, package version, service configuration, toolchain facts, matrix configuration, and artifact locations.
A shortened manifest excerpt has this general shape:
{ "runId": "run identifier generated by the package", "package": { "version": "package version recorded during the run", "root": "package root recorded during the run" }, "services": { "go": { "runtime": "Go version recorded during the run", "binary": "bin/go-http-server", "port": 18180 }, "javaHelidon": { "helidonVersion": "Helidon version recorded during the run", "jdk": "Java runtime recorded during the run", "javac": "Java compiler recorded during the run", "maven": "Maven version recorded during the run", "jar": "services/java-helidon/target/java-helidon-benchmark.jar", "jvmArgs": "JVM arguments recorded during the run", "port": 18181 } }, "matrix": { "configuration": "manifests/benchmark-template.json" }, "artifacts": { "root": "artifacts", "environmentManifest": "artifacts/manifests/environment-manifest.json", "runManifest": "artifacts/manifests/run-manifest.json", "validation": "artifacts/validation", "benchmark": "artifacts/benchmark", "logs": "artifacts/logs" }}
Use the generated manifest, not this abbreviated excerpt, as the source of truth for later analysis.
The manifest is the bridge between reproduction and analysis. It tells later articles which package version ran, which Go and Java runtimes were recorded, which service ports were used, where the matrix configuration came from, and where to find the validation and benchmark artifacts.
It does not say which implementation is faster. It tells you how the run was produced.
What counts as a valid run
A valid run is a benchmark execution where every required gate completes and the generated artifacts match the run manifest.
For this package, a valid run means:
- The prerequisite check passes.
- Environment facts are captured.
- Shared contract validation passes.
- Live Go-versus-Java equivalence validation passes.
- Both services build from the package source.
- Smoke checks pass for both services.
- The benchmark matrix completes for both services.
- Artifact verification passes.
- The environment and run manifests exist.
- The manifest paths match the generated artifact tree.
That definition may sound strict, but it prevents a common benchmark failure mode: treating partial output as if it were comparable evidence.
If a build fails, you do not have a benchmark. If contract validation fails, you do not have a fair comparison against this package contract. If the matrix runs but the manifest is missing, you do not have an auditable run. If artifact verification fails, later analysis should not use that run.
What to rerun when validation fails
When a gate fails, stop at the failed gate. Do not keep running commands in the hope that later output will be useful.
If the package root is wrong, go back to the directory containing scripts/, contracts/, fixtures/, services/, and tools/, then rerun the prerequisite check:
./scripts/check-prereqs.sh
If environment capture fails, fix the writable artifact path or missing tool problem, then rerun:
./scripts/capture-env.sh
If contract validation fails, fix the implementation or fixture mismatch, then rerun:
./scripts/validate-contract.sh
If smoke or matrix execution fails because generated artifacts from an earlier attempt are confusing the run boundary, the package includes a cleanup script for generated run artifacts:
./scripts/clean-run.sh
A successful cleanup prints output in this shape:
[clean-run] removed generated run artifacts[clean-run] clean boundary ready
This cleanup step removes generated run evidence. It is not a process manager. If a service is still running or a loopback port is occupied, stop that process or choose documented alternate ports before rerunning the smoke or matrix command.
Then restart from a clean boundary:
./scripts/check-prereqs.sh./scripts/capture-env.sh./scripts/validate-contract.sh
The important rule is simple: do not merge artifacts from failed and successful attempts. Keep each run’s evidence coherent, or discard it and rerun.
How this connects to later analysis
This reproduction run produces the evidence path that the remaining articles need.
The next article explains how generated data can be normalized, aggregated, modeled, and visualized. The results article presents benchmark results from valid runs. The final interpretation article discusses what those results mean for runtime choice, service design, and operational tradeoffs.
The order matters. Without this reproduction step, later charts would ask readers to trust an invisible process. With it, the process is inspectable: source, scripts, contracts, manifests, logs, validation output, and benchmark artifacts all live in the package.
That is how a benchmark earns the right to be discussed.
References
- Helidon Documentation — https://helidon.io/docs/
- Helidon SE Introduction — https://helidon.io/docs/v4/se/introduction
- Helidon 4 Introduction — https://helidon.io/docs/v4/about/intro
- Go
net/httpPackage Documentation — https://pkg.go.dev/net/http - Java Documentation — https://docs.oracle.com/en/java/
- GraalVM Native Image Documentation — https://www.graalvm.org/latest/reference-manual/native-image/
Conclusion
A reproducible benchmark starts before the first chart. It starts with a source package that another engineer can inspect, a command path that can be rerun, validation gates that fail loudly, and artifacts that connect later analysis back to the run that produced them.
In this article, we reproduced the Go net/http and Java Helidon SE benchmark environment from the package root. We checked prerequisites, captured environment facts, validated the shared contract, built both benchmark service implementations from package source, ran smoke checks, executed the matrix, and inspected the generated manifests and artifacts.
That gives the rest of the series a stable foundation. The next step is not to declare a winner. The next step is to use the generated evidence carefully, so any later result can be traced back to a valid, auditable run.




































You must be logged in to post a comment.