Key Takeaways
- This article defines what Java means for this benchmark: a Helidon SE service running on explicitly identified Oracle-built JDK variants, with a declared request-handling model, JVM policy, diagnostics, and manifest evidence.
- Virtual threads are treated as a request-handling model, not a performance claim. A later benchmark must prove which model was active and measure whether this workload benefits from it.
- Runtime labels require exact build evidence. A later result is not accepted if it only says “Java” or “Java 21” without provider, version, build, architecture, flags, and environment details.
- This article defines the Java contract only. The runnable benchmark package and benchmark results come later, after the series defines the full service contract, load generation, execution method, and analysis method.
You can make “Java” mean almost anything in a benchmark.
It might mean a plain JDK HTTP server, Spring Boot, Quarkus, Micronaut, Vert.x, Netty, or Helidon. It might mean platform threads, virtual threads, a reactive handler model, a native executable, a checkpoint/restore workflow, or an ordinary JVM run with JIT warmup.
It might mean default heap ergonomics, fixed heap settings, one garbage collector, another garbage collector, request logging on, TLS on, or a tuned command line nobody wrote down.
If a later chart labels one bar Java, that chart is already too vague.
This article defines the Java side before we measure it. For this benchmark, the Java service shape is Helidon SE. Measured Java evidence in this series is limited to explicitly identified Oracle-built JDK variants. The request-handling model must be declared. Startup and warmup boundaries must be separated. JVM tuning must be framed as a hypothesis. Diagnostics must be captured before results are accepted.
No benchmark result is published here. No winner is declared here. We are defining the Java/Helidon contract that later source code and benchmark runs must satisfy.
Where This Article Fits in the Series
This is Part 3 of a ten-part, single-machine benchmark series. Part 1 set the rule: method first, measurement later. A run is not useful because it completed; it is useful only if the service behavior, runtime identity, environment, load generator, run protocol, and analysis method are all traceable.
Part 2 defines the language-neutral service behavior. Both implementations expose the same health and work endpoints, follow the same request and response contract, and use the same posture for logging, TLS, compression, and related service settings in the primary pure-service benchmark.
Those requirements are not Java requirements or Go requirements. They are the HTTP contract both services must satisfy before either one is tuned or measured.
This article binds that neutral service contract to the Java implementation shape. It answers a practical question: when a later article says Java/Helidon, what exactly does that mean?
Part 4 applies the same discipline to the Go side. It defines the Go net/http implementation shape, runtime settings, profiling plan, and extraWork equivalence review. Parts 5 through 8 define load generation, execution, reproduction, and analysis. Results do not arrive until Part 9, with final interpretation in Part 10.
That ordering matters. If we tune Java first and define the Java contract afterward, the reader has to wonder whether the contract was shaped around the result. If we define the contract now, before source generation and before measurement, later Java results become auditable.
Later results must be generated fresh from the validated contract and preserved with the artifacts needed to audit them.
What “Java” Means in This Benchmark
For this benchmark, “Java” means a Helidon SE service that implements the finalized Article 02 endpoint contract exactly. The later implementation should make the Helidon SE WebServer routing and server configuration reviewable rather than relying on undocumented defaults.
That sentence intentionally excludes a lot. It does not say all Java services. It does not say all Java frameworks. It does not say Helidon represents Spring Boot, Quarkus, Micronaut, Vert.x, Netty, or a plain JDK server. Those are valid production choices, but they are outside the Java service shape selected for this benchmark.
Helidon SE is selected because its programmatic style can make routing and server configuration explicit in the benchmark source package. That helps reviewability for this particular contract. We want a reader to inspect the later source package and see the choices that matter, not infer them from a larger application stack.
The runnable benchmark must record the released Helidon SE version and resolved dependency set actually used for each run.
A later accepted Java run must identify the runtime shape in a manifest. This YAML is only an illustrative sketch, not the final manifest schema and not evidence that this article builds a service:
java_runtime: implementation_id: "java-helidon-se" framework: "Helidon SE" manifest_must_record: framework: - helidon_version - resolved_dependency_set jdk: - provider - version_output_file - runtime_properties_file - system_settings_file - effective_flags_file - architecture request_handling: - selected_threading_model - evidence_that_model_was_active execution_policy: - heap_policy - gc_policy - startup_boundary - warmup_boundary - measurement_boundary diagnostics: - gc_log - jfr_recording_when_enabled - process_metrics - request_metrics artifacts: - raw_result_files - summary_files - manifest_links
The important point is not the field names. The important point is the chain of evidence. A later Java result must tie together the framework, dependency set, JDK build, request-handling model, JVM flags, heap and GC posture, diagnostics, warmup window, measurement window, and raw artifacts.
The Java side becomes auditable only when the framework, JDK identity, request-handling model, JVM policy, diagnostics, and manifest are treated as one contract.
Java Runtime-Shape Map
The Java benchmark result is not valid until the Helidon SE service shape, JDK variant, request-handling model, JVM flags, diagnostics, and manifest evidence are captured from the validated implementation.

Figure 1. We define the Java runtime shape before measurement: Article 2’s service contract flows into Helidon SE, an exact JDK variant, a declared request-handling model, JVM policy, diagnostics, and a manifest before results are allowed.
The diagram is a reminder that the benchmark does not start at the load generator. It starts with the service contract. The Java implementation must satisfy that contract, record its runtime identity, and capture enough diagnostics to explain later measurements.
The Java Service Contract
The Helidon SE service must implement the finalized Article 2 language-neutral contract without adding framework-specific drift.
That means the Java service must expose the Article 2 endpoints and behavior exactly, and the later Go service must be held to the same finalized contract. The Java service must use the same request body limit, accept the same JSON media types, reject the same invalid request shapes, enforce the same numeric bounds, reject unknown and duplicate top-level request fields, return the same success fields, and return the same JSON error shape for body-bearing failures.
The primary benchmark posture also stays the same: request logging off, TLS off, compression off, and tracing off unless a later secondary slice explicitly enables one of them for both implementations. A Helidon default cannot become a hidden benchmark input.
This is where framework details matter. JSON parsing can differ in duplicate-field handling, numeric coercion, unknown-field policy, and error reporting. If the selected JSON library does not reject duplicate top-level fields by default, the Java implementation must add an explicit parsing or validation step that does. Routing can differ in how unsupported methods are handled. Server defaults can affect request size limits, connection behavior, response headers, exception mapping, and log output.
None of those details necessarily makes one framework better or worse. Hidden differences simply make results harder to trust.
The later Java source package must make these choices explicit enough for conformance tests to fail when the service drifts. A framework-default HTML error page is not a small cosmetic issue in this benchmark; it is a contract failure. Accepting "payloadSize": "1024" as a string when the contract requires an integer JSON number is also a contract failure. Returning an extra diagnostic field in /health is a contract failure.
Benchmarks often rush toward tuning. Here, correctness and equivalence come first. A fast service that answers a different question is not evidence.
Runtime Variants and Manifest Policy
Runtime labels are not vague labels in this series. They are benchmark inputs that must be backed by exact evidence.
For the measured Java evidence in this comparison, accepted Java runtime variants must be built by Oracle and labeled precisely. Non-Oracle JDK distributions are outside this measured comparison unless a later article explicitly defines a new scope and records them as separate, verified evidence. A result is not accepted unless the run records the exact provider, version, build string, architecture, effective JVM flags, operating system, and available processors as seen by the JVM.
These command shapes show the kind of runtime evidence the benchmark package needs to preserve:
mkdir -p artifactsjava -version > artifacts/java-version.txt 2>&1java -XshowSettings:properties -version > artifacts/java-runtime-properties.txt 2>&1java -XshowSettings:system -version > artifacts/java-system-settings.txt 2>&1java -XX:+PrintFlagsFinal -version > artifacts/jvm-flags.txt 2>&1
Those files preserve the basic runtime identity. artifacts/java-version.txt captures the version string, runtime build, and VM name. artifacts/java-runtime-properties.txt captures properties such as java.vendor, java.version, java.runtime.version, java.vm.name, os.name, and os.arch. artifacts/java-system-settings.txt captures system and container-related settings exposed by the selected JDK. artifacts/jvm-flags.txt captures effective JVM flags, including defaults and explicitly supplied options.
These commands do not prove benchmark correctness. They only show the kind of runtime evidence the later benchmark must preserve. -XshowSettings:system should be validated for each selected JDK and Linux environment because the exact system and container fields exposed by the launcher can vary.
If the benchmark runs inside a constrained environment, the manifest must also record CPU and memory limits visible to the JVM. JVM ergonomics use available processor and memory information. If the service sees two processors but the host has many more, that is not a footnote; it is part of the benchmark input.
The series will not infer a JDK advantage from branding. Runtime variants must run the same service contract, under the same run protocol, with comparable diagnostics, before any comparison is allowed. If a later result differs, the first question will be: what exact runtime, flags, machine state, and artifacts produced that difference?
Virtual Threads Are a Request-Handling Model, Not a Result Claim
Virtual threads matter for the Java side because they can change how the service handles concurrent requests. JEP 444 defines virtual threads as a final feature in JDK 21 and describes them as lightweight threads managed by the JDK rather than the operating system.
That does not make virtual threads a magic performance switch.
Virtual threads are especially relevant when a service uses a thread-per-task style and spends time blocked on I/O. They can make blocking-style code easier to write and scale for suitable workloads.
But they do not make CPU-bound work run faster. If the handler is mostly JSON parsing, deterministic payload generation, checksum work, allocation pressure, or CPU-bound simulation, virtual threads may not improve throughput. They may still affect scheduling behavior, memory pressure, thread diagnostics, and latency distribution.
The contract treats virtual threads as a declared request-handling model. A platform-thread versus virtual-thread comparison is allowed only if the selected Helidon SE release documents both modes in a way that keeps the framework version, endpoint code, server architecture, JVM flags, and other benchmark inputs constant. Otherwise, the benchmark should declare the supported Helidon request-handling model and omit the artificial comparison.
The important rule is isolation: if platform-thread and virtual-thread variants are compared, the benchmark must prove that only the intended request-handling variable changed.
Here is the contract idea as a small sketch, not a Helidon configuration file:
request_handling: selected_model: "declared for the run" comparison_model: "included only when supported and isolated" result_claim_allowed_from_configuration_alone: false required_evidence: - selected_helidon_configuration - jdk_version_and_flags - threading_model_declaration - representative_thread_diagnostics_when_enabled
Diagnostics matter here. Blocking behavior, synchronized regions, native calls, and pinned virtual threads can affect scalability. Pinning is not automatically a correctness bug, but it can explain why a virtual-thread service behaves differently from the mental model. Representative Java diagnostic runs should capture enough evidence to inspect thread behavior when the methodology calls for it.
So the benchmark will not say “virtual threads are faster.” It will ask a narrower question: under the Article 02 workload, with the selected Helidon SE service and selected JDK build, does this request-handling model change throughput, latency distribution, CPU use, memory use, warmup behavior, or diagnosability?
That question is measurable. The slogan is not.
Startup and Warmup Boundaries
The baseline Java execution model is an ordinary JVM run with a defined warmup boundary and a defined measurement boundary.
That sounds boring, and boring is good here. A JIT runtime needs warmup time. The service may initialize framework structures, compile hot paths, settle allocation behavior, and reach a different steady-state profile than it showed in the first few requests. Later methodology articles define the warmup interval, measurement interval, repeat count, invalid-run rules, and analysis rules.
Any Project Leyden-related startup or warmup path is handled separately and only if the selected JDK exposes a supported, reproducible workflow for the benchmark. Project Leyden focuses on startup time, time to peak performance, and footprint. If the selected JDK provides a supported Leyden-related workflow, such as ahead-of-time class loading and linking for that JDK, the benchmark may include it as a separately labeled startup and warmup variant with recorded commands, generated artifacts, checksums, and training input. If it does not, the variant is omitted.
The contract is simple:
startup_warmup_policy: baseline: mode: "ordinary-jvm" included: true warmup_boundary: "defined by later benchmark methodology" leyden_related_variant: included: "only when supported by the selected JDK" required_evidence: - jdk_version_and_build - exact_record_create_run_commands - generated_artifact_filenames - generated_artifact_checksums - training_input_description - regeneration_rule_per_application_build graalvm_native_image: included_in_this_jvm_comparison: false note: "Separate execution model; not silently mixed into JVM results."
This separation prevents a common confusion. Leyden-related HotSpot and JDK work is not the same thing as GraalVM Native Image. GraalVM Native Image compiles a Java application ahead of time into a native executable. That changes the build and runtime model. It may be worth measuring in a separate slice, but it is not silently part of the ordinary JVM comparison in this article.
Checkpoint and restore workflows belong in the same category: separate execution model unless explicitly defined. They should not slip into the primary Java/Helidon comparison just because they affect startup or warmup.
If a later article includes a supported Leyden-related path, the result must identify the generated artifacts, training traffic, checksums, commands, and regeneration rules. Startup features without artifact discipline quickly become unrepeatable anecdotes.
JVM Tuning Hypotheses
JVM tuning in this series starts as a hypothesis, not a conclusion.
A fixed heap may make memory behavior easier to compare, but it can increase GC frequency under some workloads. Ergonomic heap sizing may better reflect default production behavior, but it can make memory comparisons harder if the runtime adapts differently under constraints. A pause-time target can influence latency behavior, but it is a goal, not a hard guarantee. A different collector may help some workloads and hurt others. A setting that improves one percentile can move cost into CPU, allocation rate, RSS, warmup, or throughput.
So the primary Java baseline should be intentionally plain and reproducible before variants are added. A reasonable Java run profile might fix the selected JDK build, fix the selected Helidon SE version, declare heap policy, use a documented baseline collector such as G1 when selected, enable GC logging when methodology requires it, and capture enough runtime evidence to explain what happened.
This illustrative launch shape shows diagnostic intent. It is not a final benchmark runner, and target/java-service.jar is a placeholder for a later built service artifact rather than an artifact produced by this article:
mkdir -p logs artifactsjava -Xms512m -Xmx512m -XX:+UseG1GC -Xlog:gc*,safepoint:file=logs/gc.log:time,uptime,level,tags -XX:StartFlightRecording=filename=logs/run.jfr,settings=profile,dumponexit=true -jar target/java-service.jar
If used later against a real service artifact, this style of run would produce a GC log and a Java Flight Recorder recording for diagnostic analysis. That does not make the command optimized. It only makes the run easier to inspect.
The later methodology decides which diagnostics are enabled during primary measurement and which are enabled only for representative diagnostic runs. Java Flight Recorder and profilers are useful, but they are not free. The benchmark has to define overhead policy rather than pretending diagnostics have no effect.
JVM tuning also needs to stay connected to the workload. An application service may spend time in HTTP handling, JSON serialization, validation, request shaping, vector-search calls, embedding calls, connection pools, or model gateways. The primary benchmark in this series isolates a pure service path first. If a later dependency slice adds a database call, the Java tuning question changes because blocking I/O, driver behavior, connection pooling, and database latency enter the picture.
That is another reason not to overfit JVM flags before the benchmark contract is complete.
Diagnostics Required Before Results
Diagnostics are not extra paperwork. They are how we avoid guessing.
If a later chart shows a latency spike, we need to know whether the service was CPU-saturated, allocating heavily, collecting frequently, warming up, blocked, pinned, hitting a request limit, logging unexpectedly, or being distorted by the load generator.
If a throughput difference appears between two runtime variants, we need to know whether the same flags, heap policy, and request-handling model were actually used. If memory differs, we need heap and RSS context, not a single number detached from GC behavior.
Every accepted Java run needs enough evidence to connect the observed HTTP behavior to the runtime state that produced it. That evidence includes runtime identity, framework identity, endpoint conformance, JVM behavior, process metrics, request metrics, and manifest links back to the raw artifacts.
Runtime identity means java -version, runtime properties, system settings, effective JVM flags, operating system, architecture, and available processors as seen by the JVM. Framework identity means the Helidon SE version, dependency set, and source revision used to build the service. Endpoint conformance means the Java service passed the Article 02 contract tests before measurement. JVM behavior means heap policy and GC evidence captured under the methodology’s diagnostics policy. Process and request metrics must cover the warmup and measurement windows, not just a single end-of-run snapshot.
Representative diagnostic runs can add more detail. Java Flight Recorder is especially useful on selected JDK builds where it is available and enabled for the benchmark methodology, because it can expose JVM events relevant to allocation, GC, I/O, threads, and runtime behavior. JDK Mission Control can later inspect Java Flight Recorder recordings when diagnostic analysis is needed.
async-profiler can add sampling views for CPU and allocation behavior, but it is a separate tool with platform and permission requirements. It should be used deliberately, not assumed to be present everywhere.
The goal is not to make every benchmark run heavy. The goal is to make every accepted result explainable. Primary measurement runs and representative diagnostic runs can have different profiles, as long as the methodology records which artifacts belong to which purpose.
The later load-generator article must also define whether load is open-loop or closed-loop, how client saturation is detected, how timing is recorded, how errors are counted, and how coordinated-omission risk is handled before any latency comparison is accepted. Java diagnostics can explain the server side only if the client side is controlled well enough to trust the measurements.
Acceptance Criteria for the Later Java Implementation
This article does not generate a Helidon project, build a JAR, run a load generator, or produce benchmark results. It defines the Java side of the contract. The runnable benchmark package is deferred until the series defines the full service contract, implementation plans, load-generator validation, execution method, reproduction requirements, and analysis method.
A later Java result is not accepted unless the Java implementation and run artifacts satisfy these gates:
- The service implements the finalized Article 02 endpoint contract exactly, including JSON request rules, success responses, error responses, media-type handling, request body limit, logging posture, and TLS posture.
- The service uses Helidon SE, and the manifest records the exact framework version and resolved dependency set used in the run.
- The run records the exact JDK provider, version, build, architecture, runtime properties, system settings, effective JVM flags, heap policy, GC policy, available processors visible to the JVM, and any CPU or memory limits imposed by the execution environment.
- The measured Java runtime variants are explicitly identified Oracle-built JDK variants unless a later scope change defines and validates additional runtime evidence separately.
- The request-handling model is declared as platform threads, virtual threads, or another explicitly documented Helidon- and JDK-supported model, with evidence that the intended model was active.
- Platform-thread and virtual-thread variants are included only when the selected Helidon SE release documents both modes in a way that allows the request-handling model to change without unrelated framework, endpoint, server-architecture, or JVM-policy changes.
- Ordinary JVM execution, Leyden-related startup and warmup behavior, GraalVM Native Image, and checkpoint and restore workflows remain separate execution models unless a later article defines a distinct variant.
- The run captures GC logs when required by methodology, process metrics, request metrics, warmup and measurement timestamps, and manifest links to artifacts.
- Representative diagnostic runs include Java Flight Recorder where the methodology enables it, and selected profiler slices where platform support and permissions allow them.
- The result does not compare against Go until the Go contract, load-generator contract, execution protocol, and analysis method are complete.
The smallest evidence-capture shape looks like this:
mkdir -p artifacts logsjava -version > artifacts/java-version.txt 2>&1java -XX:+PrintFlagsFinal -version > artifacts/jvm-flags.txt 2>&1
Passing those commands is not enough to validate a benchmark. It only demonstrates the posture: runtime identity and effective flags are captured before results are reviewed.
If a later Java run is missing runtime identity, framework identity, request-handling evidence, warmup boundaries, diagnostics required by the methodology, or endpoint conformance, the run is not surprising. It is invalid.
Handoff to the Go Contract
The Java side is now specified enough to constrain later implementation work.
Java means Helidon SE. Runtime labels require exact JDK evidence. Virtual threads are a request-handling model. Leyden-related behavior is conditional and separate. JVM tuning is a hypothesis. Diagnostics and manifests gate results.
The next article applies the same discipline to Go.
That matters because Go has its own ways to drift. A compact net/http service can be easier to inspect, but it can still allocate differently, parse differently, validate differently, return different errors, or perform a different amount of work in the extraWork path. The Go implementation and tuning plan must be held against the same Article 2 service contract.
Java is specified here. Go is specified next. Results come later.
Conclusion: Make Java Auditable Before Measuring It
A defensible Go-versus-Java benchmark cannot leave Java undefined.
For this series, Java is a Helidon SE implementation of the Article 2 service contract, run on explicitly identified JDK variants, with a declared request-handling model, documented startup and warmup boundaries, testable JVM tuning hypotheses, required diagnostics, and manifest-backed evidence.
That does not make Java faster or slower. It makes the later Java result reviewable.
If you are building application services that depend on databases, model gateways, vector search, or other external systems, that is the useful habit to carry forward. Before comparing runtimes, define the service boundary. Before tuning, prove equivalence. Before publishing charts, capture the runtime evidence. Then, when results arrive, you can argue about the data instead of arguing about what was measured.
References and Methodology Notes
- Helidon documentation and Helidon SE 4 introduction for the selected Java framework shape and Helidon SE concepts.
- JEP 444: Virtual Threads for virtual-thread semantics and the important caveat that virtual threads are not faster threads.
- Java SE documentation for JDK tools, launcher options, Java Flight Recorder, and JVM behavior.
- Oracle JDK downloads for runtime distribution and build selection at benchmark time.
- Project Leyden and JEP 483: Ahead-of-Time Class Loading & Linking for Leyden-related startup and warmup context.
- GraalVM Native Image documentation for the separate native-executable model that is not silently included in ordinary JVM results.
- Java Flight Recorder API documentation and JDK Mission Control for diagnostic recording and analysis.
async-profilerfor optional representative CPU, allocation, and wall-clock profiling slices after platform compatibility is validated.





































You must be logged in to post a comment.