OpenTelemetry Quarkus Instrumentation [Java]

Alexandr Bandurchin
December 29, 2025
8 min read

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

bash
quarkus extension add opentelemetry

Maven

xml pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-opentelemetry</artifactId>
</dependency>
gradle build.gradle
implementation 'io.quarkus:quarkus-opentelemetry'

For complete Java instrumentation context, see the OpenTelemetry Java guide.

Basic Configuration

Configure OpenTelemetry in your application.properties:

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:

properties
# 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:

ComponentInstrumented By Default
REST endpointsYes
gRPCYes
Messaging (Kafka)Yes
REST ClientYes
Vert.x HTTPYes
Vert.x SQL clientsYes
Vert.x RedisYes

Controlling Automatic Instrumentation

Disable specific instrumentation via configuration:

properties
# 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:

java
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:

java
@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:

properties
# Enable metrics (experimental)
quarkus.otel.metrics.enabled=true

# Metrics export interval (default: 60s)
quarkus.otel.metric.export.interval=30s

Creating Custom Metrics

java
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:

properties
# 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:

text
14:32:15 INFO  traceId=5B8EFFF798038103D269B633813FC60C, spanId=EEE19B7EC3C1B174, sampled=true [OrderResource] (executor-thread-1) Processing order: ORD-12345

Programmatic Logging with Correlation

java
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.

properties
# 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:

properties
# 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:

properties
# 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

bash
# Maven
./mvnw package -Dnative

# Gradle
./gradlew build -Dquarkus.native.enabled=true

Native Configuration

properties
# 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:

dockerfile
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

yaml
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:

java
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:

xml Maven
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-sdk-testing</artifactId>
    <scope>test</scope>
</dependency>
gradle Gradle
testImplementation 'io.opentelemetry:opentelemetry-sdk-testing'

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 Overview

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.