Use the otel4s Java agent

Use this page when you want automatic OpenTelemetry instrumentation and also want otel4s to use the same global SDK in the same JVM application.

This page covers otel4s-opentelemetry-javaagent, a custom distribution of the upstream OpenTelemetry Java agent. The shared-context setup below applies to this distribution, not to the standard OpenTelemetry Java agent.

For background on why this setup differs from the standard agent path, see How otel4s context works with the otel4s Java agent.

otel4s-opentelemetry-javaagent is experimental.

1. Add the agent and runtime dependencies

The example below uses the sbt-javaagent plugin to attach the agent when you run the application from sbt.

lazy val service = project
  .enablePlugins(JavaAgent) // <1>
  .in(file("service"))
  .settings(
    name := "service",
    javaAgents += "io.github.irevive" % "otel4s-opentelemetry-javaagent" % "2.22.0", // <2>
    run / fork := true, // <3>
    javaOptions += "-Dcats.effect.trackFiberContext=true", // <4>
    libraryDependencies ++= Seq( // <5>
      "org.typelevel"   %% "otel4s-oteljava"                           % "1.1.0",
      "org.typelevel"   %% "otel4s-oteljava-context-storage"           % "1.1.0",
      "io.opentelemetry" % "opentelemetry-exporter-otlp"               % "1.66.0" % Runtime,
      "io.opentelemetry" % "opentelemetry-sdk-extension-autoconfigure" % "1.66.0" % Runtime
    )
  )
  1. Enable the Java agent plugin
  2. Attach otel4s-opentelemetry-javaagent
  3. Run the application in a forked JVM
  4. Enable Cats Effect fiber context tracking
  5. Add otel4s and OpenTelemetry runtime dependencies

otel4s-oteljava-context-storage and -Dcats.effect.trackFiberContext=true are required for the shared-context path shown on this page.

2. Configure the agent

The agent configures the global OpenTelemetry SDK from environment variables or system properties.

For a minimal OTLP setup, configure at least:

export OTEL_SERVICE_NAME=auth-service
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
-Dotel.service.name=auth-service
-Dotel.exporter.otlp.endpoint=http://localhost:4317

Add more agent configuration only when needed. For example, you can disable certain instrumentation:

export OTEL_INSTRUMENTATION_JDBC_ENABLED=false
-Dotel.instrumentation.jdbc.enabled=false

For the full configuration surface, see the OpenTelemetry Java agent configuration docs and disabling instrumentation docs.

3. Read the global SDK from otel4s

The agent autoconfigures the global OpenTelemetry SDK. Use OtelJava.global[IO], not OtelJava.autoConfigured[IO]().

import cats.effect.{IO, IOApp}
import org.typelevel.otel4s.context.LocalProvider
import org.typelevel.otel4s.metrics.MeterProvider
import org.typelevel.otel4s.oteljava.OtelJava
import org.typelevel.otel4s.oteljava.context.Context
import org.typelevel.otel4s.oteljava.context.IOLocalContextStorage
import org.typelevel.otel4s.trace.TracerProvider

object Main extends IOApp.Simple {
  implicit val localProvider: LocalProvider[IO, Context] =
    IOLocalContextStorage.localProvider[IO]

  def run: IO[Unit] =
    OtelJava.global[IO].flatMap { otel4s =>
      program(otel4s.meterProvider, otel4s.tracerProvider)
    }

  def program(
      meterProvider: MeterProvider[IO],
      tracerProvider: TracerProvider[IO]
  ): IO[Unit] =
    for {
      meter  <- meterProvider.get("auth-service")
      tracer <- tracerProvider.get("auth-service")

      counter <- meter.counter[Long]("service.requests").create
      _       <- counter.inc()

      _ <- tracer.span("startup").surround(IO.unit)
    } yield ()
}

With that setup:

4. Verify that the setup works

Run the application and confirm all of the following:

Typical startup logs include lines like:

[otel.javaagent ...] INFO io.opentelemetry.javaagent.tooling.VersionLogger - opentelemetry-javaagent - version: otel4s-...
IOLocalContextStorage: agent-provided IOLocal is detected

If you add your own spans with otel4s, they should appear in the same telemetry pipeline as the agent-provided instrumentation.

5. Check whether this setup fits your deployment

For background on that tradeoff, see How otel4s context works with the otel4s Java agent and the related upstream discussion in open-telemetry/opentelemetry-java-instrumentation#13576.

What's next