This guide walks through the steps to make a Java, Scala, or Kotlin service emit JVM runtime metrics in the OpenTelemetry format so that they appear in the [Runtime metrics](https://coralogix.com/docs/user-guides/apm/features/runtime-metrics/index.md) tab in APM Service Catalog.

Use this guide if your service is JVM-based but the Runtime metrics tab shows an empty state, or if you are setting up a new JVM service and want to ensure the tab is populated from day one.

OpenTelemetry semantic conventions only

The Runtime metrics tab reads only metrics that follow the [OpenTelemetry JVM semantic conventions](https://opentelemetry.io/docs/specs/semconv/runtime/jvm-metrics/) — for example, `jvm.memory.used`, `jvm.gc.duration`, `jvm.thread.count`. Metrics that follow other naming conventions (such as Micrometer's `jvm_memory_used_bytes`) are not recognized.

## What you need

- A Java, Scala, or Kotlin service running on Java 8 or later.
- **OpenTelemetry Java agent 2.0.0 or later.** Versions before 2.0 emit JVM metrics under the legacy `process.runtime.jvm.*` namespace, which the Runtime metrics tab does not query. The 2.0.0 release (12 January 2024) renamed these metrics to the stable `jvm.*` form defined by the OpenTelemetry semantic conventions. If your service is currently instrumented with a 1.x agent, upgrade before continuing.
- A Coralogix Send-Your-Data API key.
- The OpenTelemetry endpoint for your Coralogix domain.
- Permission to set environment variables or modify the JVM startup command for the service.

## Step 1: Download the OpenTelemetry Java agent

Download the latest [`opentelemetry-javaagent.jar`](https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar) from the OpenTelemetry Java instrumentation releases page.

Place the file somewhere accessible to the JVM process — for example `/opt/otel/opentelemetry-javaagent.jar`. In Kubernetes deployments, a common pattern is to mount it via an init container.

## Step 2: Enable the metrics exporter

The OpenTelemetry Java agent does not send metrics by default — its default metrics exporter is `none`. To enable JVM runtime metrics, set the metrics exporter explicitly.

```bash
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_EXPORTER_OTLP_METRICS_PROTOCOL="grpc"
export OTEL_EXPORTER_OTLP_ENDPOINT="<coralogix-otel-endpoint>"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <CXPrivateKey>"
```

Replace `<CXPrivateKey>` with your Send-Your-Data API key and `<coralogix-otel-endpoint>` with the OpenTelemetry endpoint for your selected Coralogix domain.

## Step 3: Set the resource attributes

The Runtime metrics tab correlates metrics to a service through the `service.name` resource attribute. The instance selector additionally uses the first detected of `k8s.pod.name`, `container.name`, `host.name`, `host.id`.

```bash
export OTEL_RESOURCE_ATTRIBUTES=service.name=<ServiceName>,application.name=<CXApplicationName>,cx.application.name=<CXApplicationName>,cx.subsystem.name=<CXSubsystemName>
```

In Kubernetes, `k8s.pod.name` and other pod-level attributes are not auto-detected by the Java agent — the agent only fills in `container.id`, `host.name`, and `host.id` automatically. To populate `k8s.pod.name` and `k8s.namespace.name`, choose one of:

- **Downward API environment variables**: pass pod metadata into the container as env vars and append them to `OTEL_RESOURCE_ATTRIBUTES`. Example: `OTEL_RESOURCE_ATTRIBUTES=...,k8s.pod.name=$(POD_NAME),k8s.namespace.name=$(POD_NAMESPACE)`.
- **OpenTelemetry Operator auto-instrumentation**: install the OpenTelemetry Operator and create an `Instrumentation` custom resource. The Operator injects the Java agent and the right resource attributes automatically.
- **Collector-side enrichment**: run the [`k8sattributes`](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/k8sattributesprocessor) processor in your OpenTelemetry Collector. The processor adds Kubernetes attributes to telemetry as it passes through, so the agent itself does not need to know about Kubernetes.

Without one of these, the JVM instances selector falls back to `host.name` (the Kubernetes node), which groups multiple pods on the same node into a single filter value — too coarse for per-pod debugging.

| Attribute                     | Required                  | Purpose                                                                                                                                          |
| ----------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `service.name`                | Yes                       | Identifies the service in Service Catalog. Without it, metrics arrive but cannot be correlated to a service row.                                 |
| `service.namespace`           | Recommended               | Disambiguates services with the same name in different teams or environments.                                                                    |
| `k8s.pod.name`                | Yes for per-pod filtering | Required for the JVM instances selector to work at pod granularity. Set explicitly via the Downward API, OTel Operator, or Collector enrichment. |
| `host.name`                   | Auto-detected             | Per-host filtering when `k8s.pod.name` is not present.                                                                                           |
| `deployment.environment.name` | Recommended               | Required for the environment filter.                                                                                                             |

## Step 4: Attach the agent to the JVM

Pass the agent JAR with the `-javaagent` flag, either through `JAVA_TOOL_OPTIONS` or directly in the Java command.

**Environment variable**

```bash
export JAVA_TOOL_OPTIONS="-javaagent:/opt/otel/opentelemetry-javaagent.jar"
java -jar myapp.jar
```

**Command line**

```bash
java -javaagent:/opt/otel/opentelemetry-javaagent.jar \
     -jar myapp.jar
```

**Docker**

```dockerfile
FROM eclipse-temurin:21-jre
COPY opentelemetry-javaagent.jar /opt/otel/opentelemetry-javaagent.jar
ENV JAVA_TOOL_OPTIONS="-javaagent:/opt/otel/opentelemetry-javaagent.jar"
COPY myapp.jar /app/myapp.jar
ENTRYPOINT ["java", "-jar", "/app/myapp.jar"]
```

**Kubernetes**

Add the agent as an init container that copies the JAR into a shared volume, mount the volume in your application container, and set `JAVA_TOOL_OPTIONS` accordingly. Alternatively, use the OpenTelemetry Operator to inject the agent automatically through an `Instrumentation` custom resource.

## Step 5: Restart the service

Restart the service so it picks up the agent and the new environment variables. On startup, check the logs for the agent banner:

```text
[otel.javaagent ...] [main] INFO io.opentelemetry.javaagent.tooling.VersionLogger - opentelemetry-javaagent - version: <version>
```

If the banner does not appear, the agent is not attached. Verify the path in `-javaagent` and that the file is readable by the user the JVM runs as.

## Step 6: Verify metrics are flowing

1. In the Coralogix toolbar, go to **Explore**, then **Metrics**.
1. Search for `jvm.memory.used` filtered by your service name.
1. Confirm at least one data point has arrived in the last few minutes.

Querying by service name in Explore

The OpenTelemetry resource attribute `service.name` (with a dot) is transformed during ingestion to the indexed metric label `serviceName` (camelCase). Use `serviceName` when filtering metrics in Explore or PromQL queries. The Runtime metrics tab handles this mapping automatically — you only need to know the label name when you query the metrics directly.

If `jvm.memory.used` appears but other metrics do not, your agent version may be older than the one that exposes the full stable metric set. Upgrade to the latest `opentelemetry-javaagent.jar`.

If no `jvm.*` metrics arrive at all:

- Confirm `OTEL_METRICS_EXPORTER` is set to `otlp` (not `none`, which is the default).
- Confirm the `OTEL_EXPORTER_OTLP_HEADERS` value contains a valid Send-Your-Data API key.
- Confirm the service has network access to the OpenTelemetry endpoint for your domain.
- Check the agent logs for export errors. The agent logs OTLP failures at `WARN` level.

## Step 7: Open the Runtime metrics tab

1. In the Coralogix toolbar, navigate to **APM**, then **Service Catalog**.
1. Select your service.
1. Select the **Runtime metrics** tab.

The widgets populate as soon as `jvm.*` metrics for the service are present in the selected time window. If you still see the empty state, return to Step 6 and confirm the metrics are reaching Coralogix.

## Recommended additional configuration

These are not required for the Runtime metrics tab to render, but they make the data more useful:

- **`jvm.gc.cause` opt-in attribute**: set `-Dotel.instrumentation.runtime-telemetry.capture-gc-cause=true` to surface GC cause in widget tooltips and for query filtering. (In agent 2.x. The flag is deprecated for removal in agent 3.0, where GC cause will always be captured.)

- **JFR-streamed metrics (Java 17 or later)**: on Java 17 or later, the agent can collect higher-fidelity runtime metrics from JDK Flight Recorder in addition to the stable JMX-sourced set. Enable with two properties:

  ```bash
  -Dotel.instrumentation.runtime-telemetry.emit-experimental-jfr-metrics=true
  -Dotel.instrumentation.runtime-telemetry.experimental.prefer-jfr=true
  ```

  The first property enables the experimental JFR metric set; the second prefers JFR over JMX for metrics available from both sources.

- **Deployment markers**: emit deployment events alongside metrics to overlay deployment timestamps on the Runtime metrics charts.

## Troubleshooting

| Symptom                                                                         | Likely cause                                                                                                       | Fix                                                                                                  |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
| Runtime metrics tab is hidden                                                   | The service does not emit any spans or metrics, or its `telemetry.sdk.language` is non-JVM (Python, Go, and so on) | Confirm the agent is running and `service.name` matches the service in the catalog                   |
| Tab shows the empty state                                                       | No `jvm.*` metrics arriving for the service                                                                        | Run through Step 6 — most often `OTEL_METRICS_EXPORTER` is still set to `none`                       |
| Widgets render but per-instance filter shows only one instance                  | Resource detection has not picked up `k8s.pod.name` or `host.name`                                                 | Enable the Kubernetes resource detector or set the attribute manually via `OTEL_RESOURCE_ATTRIBUTES` |
| Tab shows the empty state and `process.runtime.jvm.*` metrics appear in Explore | Agent is on the pre-2.0 release line, which emits the legacy metric namespace                                      | Upgrade to `opentelemetry-javaagent.jar` 2.0.0 or later — the tab queries the `jvm.*` form only      |
| Some widgets are empty (Heap by pool, Live set - memory after GC)               | Agent version is missing the stable JVM semantic-convention output                                                 | Upgrade to the latest `opentelemetry-javaagent.jar` 2.x                                              |
| Metrics arrive in Explore but the catalog does not show the service             | `service.name` is missing or differs from the spans' service name                                                  | Set `service.name` consistently for traces and metrics                                               |

## Related resources

- [Runtime metrics](https://coralogix.com/docs/user-guides/apm/features/runtime-metrics/index.md)
- [Java OpenTelemetry instrumentation](https://coralogix.com/docs/opentelemetry/instrumentation-options/java-opentelemetry-instrumentation/index.md)
- [OpenTelemetry JVM semantic conventions](https://opentelemetry.io/docs/specs/semconv/runtime/jvm-metrics/)
