Zero-code | Java Agent

Zero-code instrumentation in Java works by attaching a special Java agent JAR to any Java 8+ application. The agent dynamically modifies application bytecode at runtime to capture telemetry data—without requiring you to change your application code.

This makes it possible to automatically collect traces, metrics, and logs from many popular libraries and frameworks. Typical examples include:

In other words, the agent gives you observability "for free" at the edges of your service, drastically reducing the need for manual instrumentation.


The otel4s-opentelemetry-java distribution is a variant of the official OpenTelemetry Java agent. It provides all the same automatic instrumentation as the upstream agent, but with two important additions:

You can see a working demo here: otel4s-showcase.

The otel4s-opentelemetry-java agent is experimental. The additional otel4s and Cats Effect instrumentation relies on non-standard techniques and may behave unpredictably in non-trivial environments.

Do I need an agent?

The agent is best suited for:

However, manual instrumentation with otel4s may be a better fit when:

In practice, many teams combine both approaches:

Getting started

The agent can be configured via sbt-javaagent plugin:

lazy val service = project
  .enablePlugins(JavaAgent)                                                         // <1>
  .in(file("service"))
  .settings(
    name := "service",
    javaAgents += "io.github.irevive" % "otel4s-opentelemetry-javaagent" % "0.0.3", // <2>
    run / fork := true,                                                             // <3>
    javaOptions += "-Dcats.effect.trackFiberContext=true",                          // <4>
    libraryDependencies ++= Seq(                                                    // <5>
      "org.typelevel"   %% "otel4s-oteljava"                           % "0.13.1",
      "org.typelevel"   %% "otel4s-oteljava-context-storage"           % "0.13.1",
      "io.opentelemetry" % "opentelemetry-exporter-otlp"               % "1.53.0" % Runtime,
      "io.opentelemetry" % "opentelemetry-sdk-extension-autoconfigure" % "1.53.0" % Runtime
    )
  )
  1. Enable JavaAgent sbt plugin
  2. Register otel4s-opentelemetry-javaagent as a Java agent
  3. Make sure the VM will be forked when running
  4. Enable Cats Effect fiber context tracking
  5. Add all necessary dependencies

Configuration

The agent supports the full set of official configuration options.

In addition, you can disable or suppress specific instrumentation if certain libraries or frameworks should not be traced. See Disabling Instrumentation for details.

This is especially useful when:

Configuration is typically provided via environment variables or JVM system properties, making it easy to manage in containerized and cloud environments.

Application setup

The application can be configured in the following way:

import cats.effect.{IO, IOApp, Resource} 
import org.typelevel.otel4s.context.LocalProvider
import org.typelevel.otel4s.metrics.Meter
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.Tracer

object Server extends IOApp.Simple {
  def run: IO[Unit] = {
    implicit val localProvider: LocalProvider[IO, Context] = 
      IOLocalContextStorage.localProvider[IO]                      // <1>

    for {
      otel4s <- Resource.eval(OtelJava.global[IO])                 // <2>
      meter  <- Resource.eval(otel4s.meterProvider.get("service"))
      tracer <- Resource.eval(otel4s.tracerProvider.get("service"))
      _      <- startApp(meter, tracer)
    } yield ()
  }.useForever
  
  private def startApp(meter: Meter[IO], tracer: Tracer[IO]): Resource[IO, Unit] = {
    val _ = (meter, tracer)
    Resource.unit
  }
}
  1. IOLocalContextStorage.localProvider - will automatically pick up agent's context when available
  2. OtelJava.global[IO] - you must use the global instance because the agent will autoconfigure it

If everything is configured correctly, you will see the following log messages:

[otel.javaagent 2025-07-27 09:37:18:069 +0300] [main] INFO io.opentelemetry.javaagent.tooling.VersionLogger - opentelemetry-javaagent - version: otel4s-0.0.2-otel-2.18.1
IOLocalContextStorage: agent-provided IOLocal is detected

How the instrumentation works

Cats Effect has its context propagation mechanism known as IOLocal. The 3.6.0 release provides a way to represent IOLocal as a ThreadLocal, which creates an opportunity to manipulate the context from the outside.

Limitations

For example, if you deploy two WAR files into the same Tomcat instance, both applications will attempt to configure the agent's shared bootstrap context, leading to conflicts and unpredictable behavior.

Check the https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/13576 for more information.