Skip to content

Java — manual instrumentation

No auto-instrumentation exists for OpenAI in Java. Manual spans with new semconv.

Before you start

These examples export to a local OpenTelemetry Collector over OTLP/gRPC. Deploy the collector first — see Code examples → Deploy an OpenTelemetry Collector.

Dependencies (pom.xml)

<dependencies>
    <dependency>
        <groupId>com.openai</groupId>
        <artifactId>openai-java</artifactId>
        <version>4.35.0</version>
    </dependency>
    <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-sdk</artifactId>
    </dependency>
    <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-exporter-otlp</artifactId>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-bom</artifactId>
            <version>1.59.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Environment variables

export OPENAI_API_KEY="sk-..."
export CX_OTEL_ENDPOINT="http://<COLLECTOR_HOST>:4317"

Script

package com.example;

import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.*;
// --- OTel imports ---
import io.opentelemetry.api.common.*;
import io.opentelemetry.api.trace.*;
import io.opentelemetry.context.Scope;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;

public class GenAiManualInstrumentation {
    public static void main(String[] args) {
        // --- OTel setup: configure OTLP exporter, resource, and tracer ---
        var cxEndpoint = System.getenv().getOrDefault("CX_OTEL_ENDPOINT",
                "http://<COLLECTOR_HOST>:4317");

        var exporter = OtlpGrpcSpanExporter.builder()
                .setEndpoint(cxEndpoint).build();
        var resource = Resource.getDefault().merge(Resource.create(
                Attributes.builder()
                    .put("service.name", "java-genai-demo")
                    .put("cx.application.name", "my-genai-app")
                    .put("cx.subsystem.name", "my-service").build()));
        var tracerProvider = SdkTracerProvider.builder()
                .addSpanProcessor(BatchSpanProcessor.builder(exporter).build())
                .setResource(resource).build();
        var otel = OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).build();
        var tracer = otel.getTracer("genai-manual");

        // --- Your app logic ---
        var openAi = OpenAIOkHttpClient.fromEnv();
        var model = "gpt-4o-mini";
        var systemMsg = "You are a concise assistant.";
        var userMsg = "Explain distributed tracing in one sentence.";

        // --- OTel: build gen_ai.input.messages in new semconv format ---
        var inputJson = "[" +
            "{\"role\":\"system\",\"parts\":[{\"type\":\"text\",\"content\":\"" + systemMsg + "\"}]}," +
            "{\"role\":\"user\",\"parts\":[{\"type\":\"text\",\"content\":\"" + userMsg + "\"}]}]";

        // OTel: create a GenAI span with required attributes
        Span span = tracer.spanBuilder("chat " + model)
                .setSpanKind(SpanKind.CLIENT)
                .setAttribute("gen_ai.operation.name", "chat")
                .setAttribute("gen_ai.provider.name", "openai")
                .setAttribute("gen_ai.request.model", model)
                .setAttribute("gen_ai.input.messages", inputJson)
                .startSpan();

        try (Scope ignored = span.makeCurrent()) {
            var params = ChatCompletionCreateParams.builder()
                    .model(ChatModel.GPT_4O_MINI)
                    .maxCompletionTokens(100)
                    .addSystemMessage(systemMsg)
                    .addUserMessage(userMsg).build();
            var completion = openAi.chat().completions().create(params);
            var text = completion.choices().get(0).message().content().orElse("");

            span.setAttribute("gen_ai.response.model", completion.model());
            span.setAttribute("gen_ai.response.id", completion.id());
            span.setAttribute("gen_ai.usage.input_tokens",
                    completion.usage().map(u -> u.promptTokens()).orElse(0L));
            span.setAttribute("gen_ai.usage.output_tokens",
                    completion.usage().map(u -> u.completionTokens()).orElse(0L));
            span.setAttribute("gen_ai.output.messages",
                    "[{\"role\":\"assistant\",\"parts\":[{\"type\":\"text\",\"content\":\"" +
                    text.replace("\"", "\\\"") + "\"}]}]");
            System.out.println("Response: " + text);
        } catch (Exception e) {
            span.setStatus(StatusCode.ERROR, e.getMessage());
            span.recordException(e);
        } finally {
            span.end();
        }
        tracerProvider.forceFlush();
        tracerProvider.shutdown();
    }
}

Next steps

Look up which open-source library to use for your provider in Compatibility matrix.