diff --git a/.github/workflows/lint-rest.yml b/.github/workflows/lint-rest.yml
index 89acf1dad..07a61ab2d 100644
--- a/.github/workflows/lint-rest.yml
+++ b/.github/workflows/lint-rest.yml
@@ -3,15 +3,13 @@ name: Lint What Super Linter Can't
on:
pull_request:
- push:
- branches:
- - main
-permissions:
- contents: read
+permissions: {}
jobs:
lint:
+ permissions:
+ contents: read
runs-on: ubuntu-24.04
steps:
- name: Check out
@@ -21,10 +19,15 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1
+ - name: Remap main branch URLs to PR branch for link checking
+ env:
+ GITHUB_HEAD_REF: ${{ github.head_ref }}
+ run: |
+ sed -i "/^remap = \[$/a\ \"https://github.com/prometheus/client_java/blob/main/(.*) https://github.com/prometheus/client_java/blob/${GITHUB_HEAD_REF}/\$1\"," .github/config/lychee.toml
+ sed -i "/^remap = \[$/a\ \"https://github.com/prometheus/client_java/tree/main/(.*) https://github.com/prometheus/client_java/tree/${GITHUB_HEAD_REF}/\$1\"," .github/config/lychee.toml
+
- name: Lint for pull requests
- if: github.event_name == 'pull_request'
env:
GITHUB_TOKEN: ${{ github.token }}
- GITHUB_BASE_REF: ${{ github.base_ref }}
GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: mise run lint:rest-ci
diff --git a/docs/content/instrumentation/jvm.md b/docs/content/instrumentation/jvm.md
index 804c1b09b..a9a15341f 100644
--- a/docs/content/instrumentation/jvm.md
+++ b/docs/content/instrumentation/jvm.md
@@ -3,6 +3,16 @@ title: JVM
weight: 1
---
+{{< hint type=note >}}
+
+Looking for JVM metrics that follow OTel semantic
+conventions? See
+[OTel JVM Runtime Metrics]({{< relref "../otel/jvm-runtime-metrics.md" >}})
+for an alternative based on OpenTelemetry's
+runtime-telemetry module.
+
+{{< /hint >}}
+
The JVM instrumentation module provides a variety of out-of-the-box JVM and process metrics. To use
it, add the following dependency:
diff --git a/docs/content/otel/jvm-runtime-metrics.md b/docs/content/otel/jvm-runtime-metrics.md
new file mode 100644
index 000000000..4d7904ae0
--- /dev/null
+++ b/docs/content/otel/jvm-runtime-metrics.md
@@ -0,0 +1,236 @@
+---
+title: JVM Runtime Metrics
+weight: 4
+---
+
+OpenTelemetry's
+[runtime-telemetry](https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/runtime-telemetry)
+module is an alternative to
+[prometheus-metrics-instrumentation-jvm]({{< relref "../instrumentation/jvm.md" >}})
+for users who want JVM metrics following OTel semantic conventions.
+
+Key advantages:
+
+- Metric names follow
+ [OTel semantic conventions](https://opentelemetry.io/docs/specs/semconv/runtime/jvm-metrics/)
+- Java 17+ JFR support (context switches, network I/O,
+ lock contention, memory allocation)
+- Alignment with the broader OTel ecosystem
+
+Since OpenTelemetry's `opentelemetry-exporter-prometheus`
+already depends on this library's `PrometheusRegistry`,
+no additional code is needed in this library — only the
+OTel SDK wiring shown below.
+
+## Dependencies
+
+{{< tabs "jvm-runtime-deps" >}}
+{{< tab "Gradle" >}}
+
+```groovy
+implementation 'io.opentelemetry:opentelemetry-sdk'
+implementation 'io.opentelemetry:opentelemetry-exporter-prometheus'
+
+// Pick ONE of the following:
+// Java 8+:
+implementation 'io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8'
+// Java 17+ (adds JFR-based metrics):
+// implementation 'io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17'
+```
+
+{{< /tab >}}
+{{< tab "Maven" >}}
+
+```xml
+
+ io.opentelemetry
+ opentelemetry-sdk
+
+
+ io.opentelemetry
+ opentelemetry-exporter-prometheus
+
+
+
+
+
+ io.opentelemetry.instrumentation
+ opentelemetry-runtime-telemetry-java8
+
+
+
+```
+
+{{< /tab >}}
+{{< /tabs >}}
+
+## Standalone Setup
+
+If you **only** want OTel runtime metrics exposed as
+Prometheus, without any Prometheus Java client metrics:
+
+```java
+import io.opentelemetry.exporter.prometheus.PrometheusHttpServer;
+import io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetrics;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.metrics.SdkMeterProvider;
+
+PrometheusHttpServer prometheusServer =
+ PrometheusHttpServer.builder()
+ .setPort(9464)
+ .build();
+
+OpenTelemetrySdk openTelemetry =
+ OpenTelemetrySdk.builder()
+ .setMeterProvider(
+ SdkMeterProvider.builder()
+ .registerMetricReader(prometheusServer)
+ .build())
+ .build();
+
+RuntimeMetrics runtimeMetrics =
+ RuntimeMetrics.builder(openTelemetry).build();
+
+// Close on shutdown to stop JMX metric collection
+Runtime.getRuntime()
+ .addShutdownHook(new Thread(runtimeMetrics::close));
+
+// Scrape at http://localhost:9464/metrics
+```
+
+## Combined with Prometheus Java Client Metrics
+
+If you already have Prometheus Java client metrics and want to
+add OTel runtime metrics to the **same** `/metrics`
+endpoint, use `PrometheusMetricReader` to bridge OTel
+metrics into a `PrometheusRegistry`:
+
+```java
+import io.prometheus.metrics.core.metrics.Counter;
+import io.prometheus.metrics.exporter.httpserver.HTTPServer;
+import io.prometheus.metrics.model.registry.PrometheusRegistry;
+import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
+import io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetrics;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.metrics.SdkMeterProvider;
+
+PrometheusRegistry registry =
+ new PrometheusRegistry();
+
+// Register Prometheus metrics as usual
+Counter myCounter = Counter.builder()
+ .name("my_requests_total")
+ .register(registry);
+
+// Bridge OTel metrics into the same registry
+PrometheusMetricReader reader =
+ PrometheusMetricReader.create();
+registry.register(reader);
+
+OpenTelemetrySdk openTelemetry =
+ OpenTelemetrySdk.builder()
+ .setMeterProvider(
+ SdkMeterProvider.builder()
+ .registerMetricReader(reader)
+ .build())
+ .build();
+
+RuntimeMetrics runtimeMetrics =
+ RuntimeMetrics.builder(openTelemetry).build();
+Runtime.getRuntime()
+ .addShutdownHook(new Thread(runtimeMetrics::close));
+
+// Expose everything on one endpoint
+HTTPServer.builder()
+ .port(9400)
+ .registry(registry)
+ .buildAndStart();
+```
+
+The [examples/example-otel-jvm-runtime-metrics](https://github.com/prometheus/client_java/tree/main/examples/example-otel-jvm-runtime-metrics)
+directory has a complete runnable example.
+
+## Configuration
+
+The `RuntimeMetricsBuilder` supports two configuration
+options:
+
+### `captureGcCause()`
+
+Adds a `jvm.gc.cause` attribute to the `jvm.gc.duration`
+metric, indicating why the garbage collection occurred
+(e.g. `G1 Evacuation Pause`, `System.gc()`):
+
+```java
+RuntimeMetrics.builder(openTelemetry)
+ .captureGcCause()
+ .build();
+```
+
+### `emitExperimentalTelemetry()`
+
+Enables additional experimental metrics beyond the stable
+set. These are not yet part of the OTel semantic conventions
+and may change in future releases:
+
+- Buffer pool metrics (direct and mapped byte buffers)
+- Extended CPU metrics
+- Extended memory pool metrics
+- File descriptor metrics
+
+```java
+RuntimeMetrics.builder(openTelemetry)
+ .emitExperimentalTelemetry()
+ .build();
+```
+
+Both options can be combined:
+
+```java
+RuntimeMetrics.builder(openTelemetry)
+ .captureGcCause()
+ .emitExperimentalTelemetry()
+ .build();
+```
+
+Selective per-metric registration is not supported by the
+runtime-telemetry API — it is all-or-nothing with these
+two toggles.
+
+## Java 17 JFR Support
+
+The `opentelemetry-runtime-telemetry-java17` variant adds
+JFR-based metrics. You can selectively enable features:
+
+```java
+import io.opentelemetry.instrumentation.runtimemetrics.java17.JfrFeature;
+import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics;
+
+RuntimeMetrics.builder(openTelemetry)
+ .enableFeature(JfrFeature.BUFFER_METRICS)
+ .enableFeature(JfrFeature.NETWORK_IO_METRICS)
+ .enableFeature(JfrFeature.LOCK_METRICS)
+ .enableFeature(JfrFeature.CONTEXT_SWITCH_METRICS)
+ .build();
+```
+
+## Metric Names
+
+OTel metric names are converted to Prometheus format by
+the exporter. Examples:
+
+| OTel name | Prometheus name |
+| ---------------------------- | ---------------------------------- |
+| `jvm.memory.used` | `jvm_memory_used_bytes` |
+| `jvm.gc.duration` | `jvm_gc_duration_seconds` |
+| `jvm.thread.count` | `jvm_thread_count` |
+| `jvm.class.loaded` | `jvm_class_loaded` |
+| `jvm.cpu.recent_utilization` | `jvm_cpu_recent_utilization_ratio` |
+
+See [Names]({{< relref "names.md" >}}) for full details on
+how OTel names map to Prometheus names.
diff --git a/examples/example-otel-jvm-runtime-metrics/README.md b/examples/example-otel-jvm-runtime-metrics/README.md
new file mode 100644
index 000000000..a58584694
--- /dev/null
+++ b/examples/example-otel-jvm-runtime-metrics/README.md
@@ -0,0 +1,41 @@
+# OTel JVM Runtime Metrics with Prometheus HTTPServer
+
+## Build
+
+This example is built as part of the `client_java` project.
+
+```shell
+./mvnw package
+```
+
+## Run
+
+The build creates a JAR file with the example application in
+`./examples/example-otel-jvm-runtime-metrics/target/`.
+
+```shell
+java -jar ./examples/example-otel-jvm-runtime-metrics/target/example-otel-jvm-runtime-metrics.jar
+```
+
+## Manually Testing the Metrics Endpoint
+
+Accessing
+[http://localhost:9400/metrics](http://localhost:9400/metrics)
+with a Web browser should yield both a Prometheus counter metric
+and OTel JVM runtime metrics on the same endpoint.
+
+Prometheus counter:
+
+```text
+# HELP uptime_seconds_total total number of seconds since this application was started
+# TYPE uptime_seconds_total counter
+uptime_seconds_total 42.0
+```
+
+OTel JVM runtime metrics (excerpt):
+
+```text
+# HELP jvm_memory_used_bytes Measure of memory used.
+# TYPE jvm_memory_used_bytes gauge
+jvm_memory_used_bytes{jvm_memory_pool_name="G1 Eden Space",jvm_memory_type="heap"} 4194304.0
+```
diff --git a/examples/example-otel-jvm-runtime-metrics/pom.xml b/examples/example-otel-jvm-runtime-metrics/pom.xml
new file mode 100644
index 000000000..e2059e811
--- /dev/null
+++ b/examples/example-otel-jvm-runtime-metrics/pom.xml
@@ -0,0 +1,82 @@
+
+
+ 4.0.0
+
+
+ io.prometheus
+ examples
+ 1.5.0-SNAPSHOT
+
+
+ example-otel-jvm-runtime-metrics
+
+ Example - OTel JVM Runtime Metrics
+
+ Example of combining Prometheus metrics with OpenTelemetry JVM runtime metrics on one endpoint
+
+
+
+
+
+ io.opentelemetry.instrumentation
+ opentelemetry-instrumentation-bom-alpha
+ 2.24.0-alpha
+ pom
+ import
+
+
+
+
+
+
+ io.prometheus
+ prometheus-metrics-core
+ ${project.version}
+
+
+ io.prometheus
+ prometheus-metrics-exporter-httpserver
+ ${project.version}
+
+
+ io.opentelemetry
+ opentelemetry-sdk
+
+
+ io.opentelemetry
+ opentelemetry-exporter-prometheus
+
+
+ io.opentelemetry.instrumentation
+ opentelemetry-runtime-telemetry-java8
+
+
+
+
+ ${project.artifactId}
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+
+
+ package
+
+ shade
+
+
+
+
+ io.prometheus.metrics.examples.otelruntimemetrics.Main
+
+
+
+
+
+
+
+
+
diff --git a/examples/example-otel-jvm-runtime-metrics/src/main/java/io/prometheus/metrics/examples/otelruntimemetrics/Main.java b/examples/example-otel-jvm-runtime-metrics/src/main/java/io/prometheus/metrics/examples/otelruntimemetrics/Main.java
new file mode 100644
index 000000000..9adf15d28
--- /dev/null
+++ b/examples/example-otel-jvm-runtime-metrics/src/main/java/io/prometheus/metrics/examples/otelruntimemetrics/Main.java
@@ -0,0 +1,70 @@
+package io.prometheus.metrics.examples.otelruntimemetrics;
+
+import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
+import io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetrics;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.metrics.SdkMeterProvider;
+import io.prometheus.metrics.core.metrics.Counter;
+import io.prometheus.metrics.exporter.httpserver.HTTPServer;
+import io.prometheus.metrics.model.registry.PrometheusRegistry;
+import io.prometheus.metrics.model.snapshots.Unit;
+import java.io.IOException;
+
+/**
+ * Example combining Prometheus metrics with OpenTelemetry JVM runtime metrics on a single endpoint.
+ *
+ *
This demonstrates:
+ *
+ *
+ * - Registering a Prometheus counter metric
+ *
- Bridging OTel runtime metrics into the same PrometheusRegistry
+ *
- Exposing everything via the built-in HTTPServer on /metrics
+ *
+ */
+public class Main {
+
+ public static void main(String[] args) throws IOException, InterruptedException {
+
+ PrometheusRegistry registry = new PrometheusRegistry();
+
+ // 1. Register a Prometheus counter metric
+ Counter counter =
+ Counter.builder()
+ .name("uptime_seconds_total")
+ .help("total number of seconds since this application was started")
+ .unit(Unit.SECONDS)
+ .register(registry);
+
+ // 2. Create a PrometheusMetricReader and register it with the same registry.
+ // This bridges OTel metrics into the Prometheus registry.
+ PrometheusMetricReader reader = PrometheusMetricReader.create();
+ registry.register(reader);
+
+ // 3. Build the OTel SDK with the reader.
+ OpenTelemetrySdk openTelemetry =
+ OpenTelemetrySdk.builder()
+ .setMeterProvider(SdkMeterProvider.builder().registerMetricReader(reader).build())
+ .build();
+
+ // 4. Start OTel JVM runtime metrics collection.
+ // - captureGcCause() adds a jvm.gc.cause attribute to jvm.gc.duration
+ // - emitExperimentalTelemetry() enables buffer pools, extended CPU,
+ // extended memory pools, and file descriptor metrics
+ RuntimeMetrics runtimeMetrics =
+ RuntimeMetrics.builder(openTelemetry).captureGcCause().emitExperimentalTelemetry().build();
+
+ // 5. Close RuntimeMetrics on shutdown to stop JMX metric collection.
+ Runtime.getRuntime().addShutdownHook(new Thread(runtimeMetrics::close));
+
+ // 6. Expose both Prometheus and OTel metrics on a single endpoint.
+ HTTPServer server = HTTPServer.builder().port(9400).registry(registry).buildAndStart();
+
+ System.out.println(
+ "HTTPServer listening on port http://localhost:" + server.getPort() + "/metrics");
+
+ while (true) {
+ Thread.sleep(1000);
+ counter.inc();
+ }
+ }
+}
diff --git a/examples/pom.xml b/examples/pom.xml
index d0c364067..1b0059655 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -32,6 +32,7 @@
example-native-histogram
example-custom-buckets
example-prometheus-properties
+ example-otel-jvm-runtime-metrics