OpenTelemetry Quarkus Instrumentation [Java]
OpenTelemetry Quarkus integration provides comprehensive observability for Quarkus applications through the quarkus-opentelemetry extension. This integration offers automatic instrumentation for distributed tracing (enabled by default), metrics collection (experimental), and structured logging with trace correlation, all while supporting GraalVM native image compilation for cloud-native deployments.
What is Quarkus?
Quarkus is a Kubernetes-native Java framework designed for building modern, cloud-native applications with extremely fast startup times and low memory consumption. Created by Red Hat, Quarkus optimizes Java specifically for containers and serverless environments.
Key characteristics:
- Supersonic Startup: Applications start in milliseconds, not seconds
- Low Memory Footprint: Reduced memory consumption compared to traditional Java frameworks
- Native Compilation: Compiles to native executables using GraalVM for instant startup
- Developer Joy: Live coding, unified configuration, and extensive extension ecosystem
- Container-First: Designed from the ground up for Kubernetes and containerized deployments
What is OpenTelemetry?
OpenTelemetry is an open-source observability framework that aims to standardize and simplify the collection, processing, and export of telemetry data from applications and systems.
OpenTelemetry supports multiple programming languages and platforms, making it suitable for a wide range of applications and environments.
OpenTelemetry enables developers to instrument their code and collect telemetry data, which can then be exported to various OpenTelemetry backends or observability platforms for analysis and visualization.
Installation
Add the OpenTelemetry extension to your Quarkus project:
Quarkus CLI
quarkus extension add opentelemetry
Maven
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-opentelemetry</artifactId>
</dependency>
For complete Java instrumentation context, see the OpenTelemetry Java guide.
Basic Configuration
Configure OpenTelemetry in your application.properties:
# Application identification
quarkus.application.name=my-quarkus-app
# OTLP exporter endpoint
quarkus.otel.exporter.otlp.endpoint=http://localhost:4317
# Optional: Authentication headers
quarkus.otel.exporter.otlp.headers=authorization=Bearer my_secret_token
# Enable tracing (enabled by default)
quarkus.otel.traces.enabled=true
# Enable metrics (experimental, disabled by default)
quarkus.otel.metrics.enabled=true
# Enable logging (experimental, disabled by default)
quarkus.otel.logs.enabled=true
Resource Attributes
Add custom resource attributes to identify your service:
# Service metadata
quarkus.otel.resource.attributes=\
deployment.environment=production,\
service.namespace=payments,\
service.version=1.2.0,\
cloud.region=us-east-1
Automatic Instrumentation
The Quarkus OpenTelemetry extension automatically instruments common frameworks and libraries without code changes:
Controlling Automatic Instrumentation
Disable specific instrumentation via configuration:
# Disable REST Client instrumentation
quarkus.otel.instrument.rest-client=false
# Disable gRPC instrumentation
quarkus.otel.instrument.grpc=false
# Disable Vert.x instrumentation
quarkus.otel.instrument.vertx-http=false
quarkus.otel.instrument.vertx-event-bus=false
quarkus.otel.instrument.vertx-sql-client=false
Manual Instrumentation
Creating Custom Spans
Inject the Tracer and create custom spans for business logic:
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Scope;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@Path("/orders")
public class OrderResource {
@Inject
Tracer tracer;
@GET
@Path("/{id}")
public Order getOrder(String id) {
// Create a custom span
Span span = tracer.spanBuilder("get_order")
.setAttribute("order.id", id)
.startSpan();
try (Scope scope = span.makeCurrent()) {
// Your business logic
Order order = orderService.findById(id);
// Add attributes to span
span.setAttribute("order.status", order.getStatus());
span.setAttribute("order.amount", order.getAmount());
return order;
} catch (Exception e) {
// Record exceptions
span.recordException(e);
throw e;
} finally {
span.end();
}
}
}
Span Attributes and Events
Add detailed context to spans:
@Inject
Tracer tracer;
public void processPayment(Payment payment) {
Span span = tracer.spanBuilder("process_payment")
.setAttribute("payment.method", payment.getMethod())
.setAttribute("payment.amount", payment.getAmount())
.setAttribute("payment.currency", payment.getCurrency())
.startSpan();
try (Scope scope = span.makeCurrent()) {
// Add event when payment is validated
span.addEvent("payment_validated");
boolean result = paymentGateway.charge(payment);
if (result) {
span.addEvent("payment_succeeded");
span.setAttribute("payment.status", "succeeded");
} else {
span.addEvent("payment_failed");
span.setAttribute("payment.status", "failed");
}
return result;
} finally {
span.end();
}
}
Metrics Collection
Enable and configure metrics export:
# Enable metrics (experimental)
quarkus.otel.metrics.enabled=true
# Metrics export interval (default: 60s)
quarkus.otel.metric.export.interval=30s
Creating Custom Metrics
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.DoubleHistogram;
import jakarta.inject.Inject;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class OrderMetrics {
private final LongCounter ordersCounter;
private final DoubleHistogram orderAmountHistogram;
@Inject
public OrderMetrics(OpenTelemetry openTelemetry) {
Meter meter = openTelemetry.getMeter("order-service");
// Counter for total orders
this.ordersCounter = meter
.counterBuilder("orders.total")
.setDescription("Total number of orders processed")
.setUnit("1")
.build();
// Histogram for order amounts
this.orderAmountHistogram = meter
.histogramBuilder("orders.amount")
.setDescription("Distribution of order amounts")
.setUnit("USD")
.build();
}
public void recordOrder(Order order) {
ordersCounter.add(1,
Attributes.of(
AttributeKey.stringKey("status"), order.getStatus(),
AttributeKey.stringKey("region"), order.getRegion()
));
orderAmountHistogram.record(order.getAmount(),
Attributes.of(
AttributeKey.stringKey("currency"), order.getCurrency()
));
}
}
Logging Integration
Configure logs to include trace context for correlation:
# Log format with trace context
quarkus.log.console.format=%d{HH:mm:ss} %-5p traceId=%X{traceId}, spanId=%X{spanId}, sampled=%X{sampled} [%c{2.}] (%t) %s%e%n
# Enable OpenTelemetry logging
quarkus.otel.logs.enabled=true
Example output:
14:32:15 INFO traceId=5B8EFFF798038103D269B633813FC60C, spanId=EEE19B7EC3C1B174, sampled=true [OrderResource] (executor-thread-1) Processing order: ORD-12345
Programmatic Logging with Correlation
import org.jboss.logging.Logger;
import jakarta.inject.Inject;
@Path("/checkout")
public class CheckoutResource {
@Inject
Logger logger;
@POST
public Response checkout(CheckoutRequest request) {
// Logs automatically include trace context
logger.info("Starting checkout for user: " + request.getUserId());
try {
Order order = checkoutService.process(request);
logger.info("Checkout completed. Order ID: " + order.getId());
return Response.ok(order).build();
} catch (Exception e) {
logger.error("Checkout failed", e);
throw e;
}
}
}
Sampling Configuration
Control which traces are sampled and exported. For a comprehensive overview of sampling strategies, see the OpenTelemetry sampling guide.
# Sampling strategies:
# - always_on: Sample all traces (default for development)
# - always_off: Sample no traces
# - traceidratio: Sample based on trace ID (e.g., 0.1 = 10%)
# - parentbased_always_on: Respect parent decision, otherwise always sample
# - parentbased_always_off: Respect parent decision, otherwise never sample
# - parentbased_traceidratio: Respect parent decision, otherwise use ratio
quarkus.otel.traces.sampler=parentbased_traceidratio
quarkus.otel.traces.sampler.arg=0.1 # Sample 10% of traces
Protocol Configuration
Choose between gRPC and HTTP for OTLP export:
# Use gRPC (default, recommended)
quarkus.otel.exporter.otlp.protocol=grpc
quarkus.otel.exporter.otlp.endpoint=http://localhost:4317
# Or use HTTP/protobuf
quarkus.otel.exporter.otlp.protocol=http/protobuf
quarkus.otel.exporter.otlp.endpoint=http://localhost:4318
Signal-Specific Endpoints
Configure different endpoints for traces, metrics, and logs:
# Separate endpoints per signal
quarkus.otel.exporter.otlp.traces.endpoint=http://trace-collector:4317
quarkus.otel.exporter.otlp.metrics.endpoint=http://metrics-collector:4317
quarkus.otel.exporter.otlp.logs.endpoint=http://logs-collector:4317
Native Image Support
The Quarkus OpenTelemetry extension fully supports GraalVM native image compilation:
Building Native Image
# Maven
./mvnw package -Dnative
# Gradle
./gradlew build -Dquarkus.native.enabled=true
Native Configuration
# Optimize for native mode (faster startup, simpler processor)
quarkus.otel.simple=true
# Native-specific resource attributes
quarkus.otel.resource.attributes=\
deployment.environment=production,\
native.image=true
Container Image
For Docker deployments, use a minimal container image:
FROM quay.io/quarkus/quarkus-micro-image:2.0
WORKDIR /work/
COPY --chown=1001:root target/*-runner /work/application
# OpenTelemetry configuration via environment
ENV QUARKUS_OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
ENV QUARKUS_APPLICATION_NAME=my-app
EXPOSE 8080
USER 1001
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
Kubernetes Deployment
Deployment with OpenTelemetry
apiVersion: apps/v1
kind: Deployment
metadata:
name: quarkus-app
spec:
replicas: 3
selector:
matchLabels:
app: quarkus-app
template:
metadata:
labels:
app: quarkus-app
spec:
containers:
- name: app
image: my-quarkus-app:latest
env:
- name: QUARKUS_OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://otel-collector.observability:4317"
- name: QUARKUS_APPLICATION_NAME
value: "quarkus-app"
- name: QUARKUS_OTEL_RESOURCE_ATTRIBUTES
value: "deployment.environment=production,k8s.namespace=$(K8S_NAMESPACE),k8s.pod.name=$(K8S_POD_NAME)"
- name: QUARKUS_OTEL_METRICS_ENABLED
value: "true"
- name: K8S_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: K8S_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
ports:
- containerPort: 8080
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
Testing
In-Memory Exporter for Tests
Create an in-memory span exporter for integration tests:
import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Singleton;
@QuarkusTest
public class TracingTest {
@ApplicationScoped
static class TestConfig {
@Produces
@Singleton
InMemorySpanExporter inMemorySpanExporter() {
return InMemorySpanExporter.create();
}
}
@Inject
InMemorySpanExporter spanExporter;
@Test
public void testTracing() {
// Make request to your endpoint
given()
.when().get("/orders/123")
.then().statusCode(200);
// Verify spans were created
List<SpanData> spans = spanExporter.getFinishedSpanItems();
assertEquals(2, spans.size());
// Verify span attributes
SpanData span = spans.get(0);
assertEquals("get_order", span.getName());
assertEquals("123", span.getAttributes().get(
AttributeKey.stringKey("order.id")));
}
}
Test dependency:
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-testing</artifactId>
<scope>test</scope>
</dependency>
What is Uptrace?
Uptrace is a OpenTelemetry APM that supports distributed tracing, metrics, and logs. You can use it to monitor applications and troubleshoot issues.

Uptrace comes with an intuitive query builder, rich dashboards, alerting rules with notifications, and integrations for most languages and frameworks.
Uptrace can process billions of spans and metrics on a single server and allows you to monitor your applications at 10x lower cost.
In just a few minutes, you can try Uptrace by visiting the cloud demo (no login required) or running it locally with Docker. The source code is available on GitHub.
FAQ
Does Quarkus OpenTelemetry extension work with GraalVM native images? Yes, the quarkus-opentelemetry extension fully supports GraalVM native compilation. You can build native executables with full tracing, metrics, and logging capabilities enabled. The extension is optimized for AOT compilation, ensuring sub-millisecond startup times while maintaining complete observability. Use quarkus.otel.simple=true for serverless environments to reduce initialization overhead.
What's the difference between Quarkus OpenTelemetry extension and the Java agent? The Quarkus extension is purpose-built for Quarkus and doesn't require the OpenTelemetry Java agent. The extension provides compile-time instrumentation that's native image compatible, while the Java agent uses runtime bytecode manipulation and doesn't support native compilation. Quarkus instrumentation is more efficient and integrates directly with Quarkus lifecycle and configuration systems.
Are metrics and logging enabled by default in Quarkus OpenTelemetry? No, only tracing is enabled by default (quarkus.otel.traces.enabled=true). Metrics (quarkus.otel.metrics.enabled) and logs (quarkus.otel.logs.enabled) are experimental features that must be explicitly enabled. This design prioritizes production stability - tracing is stable while metrics and logging capabilities continue to evolve based on community feedback.
How do I disable specific automatic instrumentation in Quarkus? Use the quarkus.otel.instrument.* configuration properties to toggle individual instrumentations. For example, set quarkus.otel.instrument.rest-client=false to disable REST client tracing or quarkus.otel.instrument.grpc=false to disable gRPC. This granular control is useful when you want manual instrumentation only or need to reduce overhead for specific components.
Can I use Quarkus OpenTelemetry with Spring Boot applications? While both frameworks support OpenTelemetry, they use different extensions. Quarkus uses quarkus-opentelemetry while Spring Boot uses the Spring Boot Starter. However, both emit OTLP-compatible telemetry that can be collected by the same OpenTelemetry Collector, enabling unified observability across polyglot microservices architectures.
How do I correlate Quarkus logs with distributed traces? Enable OpenTelemetry logging (quarkus.otel.logs.enabled=true) and configure your log format to include MDC fields: traceId=%X{traceId}, spanId=%X{spanId}, and sampled=%X{sampled}. Quarkus automatically injects these fields when logging is enabled, allowing you to search logs by trace ID or navigate from traces to related log entries in your observability platform. See context propagation for more details on trace context.
What's next?
Quarkus is now instrumented with OpenTelemetry, providing automatic distributed tracing, metrics collection, and log correlation. For alternative Java frameworks, explore Spring Boot integration. For logging framework integration, see Logback or Log4j guides. For complete Java instrumentation, refer to the OpenTelemetry Java guide.