Operations 15 min read

Step-by-Step Guide: Integrating OpenTelemetry Tracing in Java and Go Projects

This tutorial walks through setting up OpenTelemetry tracing from scratch for both Java and Go microservices, covering collector and Jaeger deployment, required dependencies, configuration parameters, code examples for automatic and manual instrumentation, and how to add custom span attributes and spans.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Step-by-Step Guide: Integrating OpenTelemetry Tracing in Java and Go Projects

Background

This guide demonstrates a hands‑on integration of OpenTelemetry tracing using two languages: Java (automatic instrumentation via the OpenTelemetry Java agent) and Go (manual instrumentation via the SDK). The example consists of a Spring Boot gRPC client, a Go gRPC server, an OpenTelemetry Collector, and a Jaeger UI for trace visualization.

Project Structure

java-demo – Spring Boot client that sends gRPC requests (OpenTelemetry Java agent 2.4.0, Spring Boot 2.7.14).

k8s-combat – Go gRPC server (OpenTelemetry Collector 0.98.0, Go 1.22).

Jaeger – All‑in‑one Jaeger container for trace storage and UI (jaegertracing/all-in-one:1.56).

opentelemetry-collector-contrib – Collector service that receives OTLP data and forwards it to Jaeger.

Deploy Collector and Jaeger

docker run --rm -d --name jaeger \
  -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
  -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 \
  -p 16686:16686 -p 4317:4317 -p 4318:4318 \
  -p 14250:14250 -p 14268:14268 -p 14269:14269 \
  -p 9411:9411 \
  jaegertracing/all-in-one:1.56

docker run --rm -d -v $(pwd)/coll-config.yaml:/etc/otelcol-contrib/config.yaml \
  --name coll -p 5318:4318 -p 5317:4317 \
  otel/opentelemetry-collector-contrib:0.98.0

The collector configuration ( coll-config.yaml) defines an OTLP receiver, a batch processor, a debug exporter, and a pipeline that sends traces to the Jaeger endpoint ( 127.0.0.1:4317).

Java Application

The Java side uses grpc-spring-boot-starter and a simple helloworld.proto that defines a SayHello RPC. The gRPC client configuration (in application.yml) is:

grpc:
  server:
    port: 9192
  client:
    greeter:
      address: 'static://127.0.0.1:50051'
      enableKeepAlive: true
      keepAliveWithoutCalls: true
      negotiationType: plaintext

Tracing is enabled by launching the Java process with the OpenTelemetry Java agent and a set of system properties:

java -javaagent:opentelemetry-javaagent-2.4.0.jar \
  -Dotel.traces.exporter=otlp \
  -Dotel.metrics.exporter=otlp \
  -Dotel.logs.exporter=none \
  -Dotel.service.name=demo \
  -Dotel.exporter.otlp.protocol=grpc \
  -Dotel.propagators=tracecontext,baggage \
  -Dotel.exporter.otlp.endpoint=http://127.0.0.1:5317 \
  -jar target/demo-0.0.1-SNAPSHOT.jar

Key parameters: otel.traces.exporter – destination for trace data (OTLP to the collector). otel.metrics.exporter – destination for metric data (used later). otel.service.name – logical service name appearing in traces. otel.exporter.otlp.protocol – transport protocol (grpc or http/protobuf). otel.propagators – context propagation formats (tracecontext, baggage, etc.).

After starting the collector, Jaeger, and the Java client, a request such as curl http://127.0.0.1:9191/request?name=demo generates a trace visible in the Jaeger UI ( http://localhost:16686).

Go Application

The Go side pulls the OpenTelemetry SDK packages and registers gRPC instrumentation:

go get "go.opentelemetry.io/otel" \
  "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" \
  "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" \
  "go.opentelemetry.io/otel/propagation" \
  "go.opentelemetry.io/otel/sdk/metric" \
  "go.opentelemetry.io/otel/sdk/resource" \
  "go.opentelemetry.io/otel/sdk/trace" \
  "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"

Tracer provider initialization with an OTLP gRPC exporter and a resource that captures OS, process, and container information:

func initTracerProvider() *sdktrace.TracerProvider {
    ctx := context.Background()
    exporter, err := otlptracegrpc.New(ctx)
    if err != nil {
        log.Printf("new otlp trace grpc exporter failed: %v", err)
    }
    tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(initResource()),
    )
    otel.SetTracerProvider(tp)
    otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
        propagation.TraceContext{}, propagation.Baggage{}))
    return tp
}

func initResource() *sdkresource.Resource {
    initResourcesOnce.Do(func(){
        extra, _ := sdkresource.New(context.Background(),
            sdkresource.WithOS(),
            sdkresource.WithProcess(),
            sdkresource.WithContainer(),
            sdkresource.WithHost())
        resource, _ = sdkresource.Merge(sdkresource.Default(), extra)
    })
    return resource
}

Start the gRPC server with the OpenTelemetry stats handler so that each incoming RPC automatically creates a span:

s := grpc.NewServer(
    grpc.StatsHandler(otelgrpc.NewServerHandler()),
)
pb.RegisterGreeterServer(s, &server{})

Manual spans can be created when needed:

ctx, span := tracer.Start(ctx, "hello-span")
defer span.End()
log.Printf("create span")

Custom Span Attributes and Events

Additional data can be attached to the current span using SetAttributes (Go) or the equivalent API in Java:

span := trace.SpanFromContext(ctx)
span.SetAttributes(attribute.String("request.name", in.Name))

Both languages also support adding events and links to a span via AddEvent and AddLink methods.

Creating New Spans

In Go, a new span is started explicitly with tracer.Start. In Java, the @WithSpan annotation (from opentelemetry-instrumentation-annotations) automatically creates a span around the annotated method:

@WithSpan("span")
public void span(@SpanAttribute("request.name") String name) throws InterruptedException {
    TimeUnit.SECONDS.sleep(1);
    log.info("span:{}", name);
}

Summary

OpenTelemetry provides automatic instrumentation for languages that support agents (e.g., Java) and manual SDK‑based instrumentation for languages like Go. The end‑to‑end example shows how to deploy a collector and Jaeger, configure a Java Spring Boot client with the Java agent, and set up a Go gRPC server with explicit tracer and resource initialization. It also demonstrates adding custom attributes, events, and manually created spans, preparing the reader for the next topic—metrics.

Source code for the Go demo: https://github.com/crossoverJie/k8s-combat. Reference for Java configuration: https://opentelemetry.io/docs/languages/java/configuration/.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaObservabilityGoOpenTelemetryDistributed Tracingtracing
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.