This guide shows you how to send custom and JVM runtime metrics from your Java application to SigNoz using the OpenTelemetry Java agent.
Prerequisites
- Java 8 or later (Java 17+ recommended for enhanced JVM metrics)
- A SigNoz Cloud account or self-hosted SigNoz instance
Send metrics to SigNoz
Step 1. Download the OpenTelemetry Java agent
wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar
Step 2. Set environment variables
export OTEL_SERVICE_NAME="<service-name>"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
export OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_METRIC_EXPORT_INTERVAL="60000"
export OTEL_TRACES_EXPORTER="none"
export OTEL_LOGS_EXPORTER="none"
Replace the following:
<region>: Your SigNoz Cloud region (us,eu, orin). See endpoints.<your-ingestion-key>: Your SigNoz ingestion key.<service-name>: A descriptive name for your service (e.g.,payment-service).
Step 3. Add OpenTelemetry API dependency
Add the OpenTelemetry API to your project. The agent provides the implementation at runtime.
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
<version>1.57.0</version>
</dependency>
implementation 'io.opentelemetry:opentelemetry-api:1.57.0'
The version above is current as of this writing. Check the OpenTelemetry Java releases for the latest versions.
Step 4. Add custom metrics to your application
Use GlobalOpenTelemetry.getMeter() to create metrics. The agent configures the MeterProvider automatically.
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.Meter;
public class OrderService {
private static final Meter meter = GlobalOpenTelemetry.getMeter("order-service");
private static final LongCounter ordersCounter = meter
.counterBuilder("orders.processed")
.setDescription("Total number of orders processed")
.build();
public void processOrder(String orderId) {
// Your business logic here
ordersCounter.add(1);
}
}
This example shows a Counter, which only increases. OpenTelemetry supports other metric types like UpDownCounter, Histogram, and Observable Gauge. See Custom Metrics Examples for complete examples of each type.
Step 5. Run your application
java -javaagent:./opentelemetry-javaagent.jar -jar your-app.jar
Step 1. Add the agent to your Dockerfile
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY target/<my-app>.jar app.jar
RUN wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar -O opentelemetry-javaagent.jar
ENTRYPOINT ["java", "-javaagent:/app/opentelemetry-javaagent.jar", "-jar", "app.jar"]
Step 2. Add environment variables to your deployment
env:
- name: OTEL_SERVICE_NAME
value: '<service-name>'
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: 'https://ingest.<region>.signoz.cloud:443'
- name: OTEL_EXPORTER_OTLP_HEADERS
value: 'signoz-ingestion-key=<your-ingestion-key>'
- name: OTEL_METRICS_EXPORTER
value: 'otlp'
- name: OTEL_METRIC_EXPORT_INTERVAL
value: '60000'
- name: OTEL_TRACES_EXPORTER
value: 'none'
- name: OTEL_LOGS_EXPORTER
value: 'none'
Replace <region>, <your-ingestion-key>, and <service-name> with your values.
Step 3. Add OpenTelemetry API and custom metrics
Follow Step 3 and Step 4 from the VM tab to add the dependency and custom metrics code.
Step 1. Download the OpenTelemetry Java agent
Invoke-WebRequest -Uri "https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar" -OutFile "opentelemetry-javaagent.jar"
Step 2. Set environment variables
$env:OTEL_SERVICE_NAME = "<service-name>"
$env:OTEL_EXPORTER_OTLP_ENDPOINT = "https://ingest.<region>.signoz.cloud:443"
$env:OTEL_EXPORTER_OTLP_HEADERS = "signoz-ingestion-key=<your-ingestion-key>"
$env:OTEL_METRICS_EXPORTER = "otlp"
$env:OTEL_METRIC_EXPORT_INTERVAL = "60000"
$env:OTEL_TRACES_EXPORTER = "none"
$env:OTEL_LOGS_EXPORTER = "none"
Replace <region>, <your-ingestion-key>, and <service-name> with your values.
Step 3. Add OpenTelemetry API and custom metrics
Follow Step 3 and Step 4 from the VM tab to add the dependency and custom metrics code.
Step 4. Run your application
java -javaagent:.\opentelemetry-javaagent.jar -jar your-app.jar
Step 1. Add the agent to your Dockerfile
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY target/<my-app>.jar app.jar
RUN wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar -O opentelemetry-javaagent.jar
ENV OTEL_SERVICE_NAME="<service-name>"
ENV OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
ENV OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
ENV OTEL_METRICS_EXPORTER="otlp"
ENV OTEL_METRIC_EXPORT_INTERVAL="60000"
ENV OTEL_TRACES_EXPORTER="none"
ENV OTEL_LOGS_EXPORTER="none"
ENTRYPOINT ["java", "-javaagent:/app/opentelemetry-javaagent.jar", "-jar", "app.jar"]
Replace <my-app>.jar, <region>, <your-ingestion-key>, and <service-name> with your values.
Step 2. Add OpenTelemetry API and custom metrics
Follow Step 3 and Step 4 from the VM tab to add the dependency and custom metrics code.
Step 3. Build and run
docker build -t my-java-app .
docker run -p 8080:8080 my-java-app
Or pass environment variables at runtime:
docker run -p 8080:8080 \
-e OTEL_SERVICE_NAME="my-service" \
-e OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.us.signoz.cloud:443" \
-e OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<key>" \
-e OTEL_METRICS_EXPORTER="otlp" \
-e OTEL_METRIC_EXPORT_INTERVAL="60000" \
-e OTEL_TRACES_EXPORTER="none" \
-e OTEL_LOGS_EXPORTER="none" \
my-java-app
The Java agent automatically generates HTTP metrics (http.server.request.duration and http.client.request.duration) for instrumented web frameworks like Spring Boot, JAX-RS, and Servlet containers. See the HTTP metrics semantic conventions for details.
Validate
Once your application is running with the Java agent:
- Trigger some application activity to generate metrics.
- In SigNoz, go to Metrics > Metrics Explorer.
- Search for your custom metric (e.g.,
orders.processed). - JVM metrics like
jvm.memory.usedandjvm.cpu.timeare collected automatically by the agent. See JVM Runtime Metrics for the full list.
JVM Runtime Metrics
The Java agent automatically collects JVM runtime metrics with zero configuration. These metrics are exported alongside any custom metrics you define, providing visibility into memory usage, garbage collection, threads, and CPU utilization.
Exported Metrics
| Category | Metric | Description |
|---|---|---|
| Memory | jvm.memory.used | Current memory usage by memory pool |
jvm.memory.committed | Committed memory by memory pool | |
jvm.memory.limit | Maximum memory available by memory pool | |
jvm.memory.used_after_last_gc | Memory used after the last garbage collection | |
| GC | jvm.gc.duration | Time spent in garbage collection (histogram) |
| Threads | jvm.thread.count | Number of executing threads |
| Classes | jvm.class.loaded | Number of classes loaded since JVM start |
jvm.class.unloaded | Total number of classes unloaded | |
jvm.class.count | Number of classes currently loaded | |
| CPU | jvm.cpu.time | CPU time used by the process |
jvm.cpu.count | Number of available processors | |
jvm.cpu.recent_utilization | Recent CPU utilization |
Visualize Runtime Metrics
Import the pre-built JVM dashboard to visualize these metrics:
- Download the JVM dashboard JSON
- In SigNoz, go to Dashboards → New Dashboard → Import JSON
- Paste the JSON content or upload the file
See the JVM Dashboard Template for details on available panels.
Custom Metrics Examples
OpenTelemetry provides four metric instrument types. Each example shows how to create and use the instrument.
Counter
A value that only increases (e.g., total requests, orders processed).
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.Meter;
Meter meter = GlobalOpenTelemetry.getMeter("my-service");
LongCounter requestCounter = meter
.counterBuilder("http.requests.total")
.setDescription("Total number of HTTP requests")
.build();
// Increment the counter
requestCounter.add(1);
UpDownCounter
A value that can increase or decrease (e.g., queue size, active connections).
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.metrics.LongUpDownCounter;
import io.opentelemetry.api.metrics.Meter;
Meter meter = GlobalOpenTelemetry.getMeter("my-service");
LongUpDownCounter activeRequests = meter
.upDownCounterBuilder("http.requests.active")
.setDescription("Number of active requests")
.build();
// Increment when request starts
activeRequests.add(1);
// Decrement when request ends
activeRequests.add(-1);
Histogram
A distribution of values (e.g., request duration, response size).
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.metrics.DoubleHistogram;
import io.opentelemetry.api.metrics.Meter;
Meter meter = GlobalOpenTelemetry.getMeter("my-service");
DoubleHistogram requestDuration = meter
.histogramBuilder("http.request.duration")
.setDescription("HTTP request duration")
.setUnit("ms")
.build();
// Record a duration value
long startTime = System.currentTimeMillis();
// ... process request ...
requestDuration.record(System.currentTimeMillis() - startTime);
Gauge
A point-in-time value via callback (e.g., temperature, memory usage).
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.ObservableDoubleGauge;
Meter meter = GlobalOpenTelemetry.getMeter("my-service");
ObservableDoubleGauge memoryGauge = meter
.gaugeBuilder("app.memory.usage")
.setDescription("Current memory usage")
.setUnit("By")
.buildWithCallback(measurement -> {
Runtime runtime = Runtime.getRuntime();
measurement.record(runtime.totalMemory() - runtime.freeMemory());
});
Troubleshooting
Metrics not appearing?
Check environment variables:
echo $OTEL_EXPORTER_OTLP_ENDPOINT
echo $OTEL_METRICS_EXPORTER
Enable debug logging:
OTEL_LOG_LEVEL=debug java -javaagent:./opentelemetry-javaagent.jar -jar app.jar
Verify agent is attached: Look for this log line on startup:
[otel.javaagent] INFO io.opentelemetry.javaagent.tooling.VersionLogger - opentelemetry-javaagent - version: X.X.X
GlobalOpenTelemetry.getMeter() returns no-op?
Make sure the Java agent is attached. Without the agent, GlobalOpenTelemetry returns a no-op implementation.
# Correct - agent flag before -jar
java -javaagent:./agent.jar -jar app.jar
# Wrong - agent flag after -jar is ignored
java -jar app.jar -javaagent:./agent.jar
Metrics delayed or not updating?
OTEL_METRIC_EXPORT_INTERVAL controls how often metrics are exported in milliseconds. The default is 60000 (60 seconds), so metrics may take up to a minute to appear.
To reduce the interval for testing:
export OTEL_METRIC_EXPORT_INTERVAL=10000 # 10 seconds
Authentication errors
If you see "Unauthorized" or "403 Forbidden":
- Verify your ingestion key is correct in
OTEL_EXPORTER_OTLP_HEADERS - Ensure the header format is exactly:
signoz-ingestion-key=<your-key>(no extra spaces)
Setup OpenTelemetry Collector (Optional)
What is the OpenTelemetry Collector?
Think of the OTel Collector as a middleman between your app and SigNoz. Instead of your application sending data directly to SigNoz, it sends everything to the Collector first, which then forwards it along.
Why use it?
- Cleaning up data - Filter out noisy metrics you don't care about, or remove sensitive info before it leaves your servers.
- Keeping your app lightweight - Let the Collector handle batching, retries, and compression instead of your application code.
- Adding context automatically - The Collector can tag your data with useful info like which Kubernetes pod or cloud region it came from.
- Future flexibility - Want to send data to multiple backends later? The Collector makes that easy without changing your app.
See Switch from direct export to Collector for step-by-step instructions to convert your setup.
For more details, see Why use the OpenTelemetry Collector? and the Collector configuration guide.
Next Steps
- Create Dashboards to visualize your custom metrics.
- Set up Alerts on your metrics.
- Instrument your Java application with traces and logs for complete observability.