> ## Documentation Index
> Fetch the complete documentation index at: https://arize-ax.mintlify.site/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Google ADK for Java

> Trace Google ADK for Java agents with OpenInference and send spans to Arize AX for LLM observability.

[Google ADK for Java](https://github.com/google/adk-java) is the JVM port of Google's Agent Development Kit — a framework for building agents with Gemini models, function tools, and multi-agent workflows. ADK already emits its own OpenTelemetry spans; the [`com.arize:openinference-instrumentation-adk-java`](https://central.sonatype.com/artifact/com.arize/openinference-instrumentation-adk-java) Java agent decorates them in place with OpenInference span kinds, prompts and completions, tool arguments and results, token counts, and session and user IDs. Your agent code stays untouched — the only application-side change is registering a global OpenTelemetry SDK that points at Arize AX.

## Prerequisites

* Java 17+ (Google ADK for Java is compiled to Java 17 bytecode) and Gradle 8+
* An Arize AX account ([sign up](https://arize.com/sign-up/))
* A `GOOGLE_API_KEY` from [Google AI Studio](https://aistudio.google.com/app/apikey)

## Launch Arize AX

1. Sign in to your [Arize AX account](https://app.arize.com/).
2. From **Space Settings**, copy your **Space ID** and **API Key**. You will set them as `ARIZE_SPACE_ID` and `ARIZE_API_KEY` below.

## Install

The instrumentation is a `-javaagent`, not a library, so it is never on your compile classpath. Resolve the shaded `all` jar into its own Gradle configuration and hand it to the JVM at launch:

```groovy theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
plugins {
    id 'application'
}

repositories {
    mavenCentral()
}

// The OpenInference ADK instrumentation ships as a `-javaagent`, so the
// shaded `all` jar is resolved here and passed to the JVM at launch
// rather than added to the compile classpath. The `@jar` suffix keeps
// the dependency non-transitive so `singleFile` resolves to one jar.
configurations {
    openinferenceAgent
}

dependencies {
    openinferenceAgent(
        'com.arize:openinference-instrumentation-adk-java:0.1.1:all@jar')

    // Pinned deliberately: the agent advises `com.google.adk.Telemetry`,
    // which ADK renamed to `com.google.adk.telemetry.Tracing` in 0.6.0.
    // Only google-adk 0.1.0-0.5.0 are instrumented today.
    implementation 'com.google.adk:google-adk:0.4.0'

    // OpenTelemetry SDK + OTLP exporter
    implementation platform('io.opentelemetry:opentelemetry-bom:1.50.0')
    implementation 'io.opentelemetry:opentelemetry-sdk'
    implementation 'io.opentelemetry:opentelemetry-exporter-otlp'

    runtimeOnly 'org.slf4j:slf4j-simple:2.0.17'
}

application {
    mainClass = 'example.Main'
}

tasks.named('run') {
    doFirst {
        jvmArgs "-javaagent:${configurations.openinferenceAgent.singleFile}"
    }
}
```

## Configure credentials

```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
export ARIZE_SPACE_ID="<your-space-id>"
export ARIZE_API_KEY="<your-api-key>"
export ARIZE_PROJECT_NAME="google-adk-java-tracing-example"
export GOOGLE_API_KEY="<your-google-api-key>"
```

## Setup tracing

Java doesn't separate setup from runtime the way Python or TypeScript do — both happen in the same `main` method. Register the OpenTelemetry SDK first, then build your ADK objects:

```java theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
// src/main/java/example/Main.java
package example;

import com.google.adk.agents.LlmAgent;
import com.google.adk.runner.InMemoryRunner;
import com.google.adk.sessions.Session;
import com.google.adk.tools.Annotations.Schema;
import com.google.adk.tools.FunctionTool;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.propagation.ContextPropagators;
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;

import java.time.Duration;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public class Main {

    /** Canned weather lookup, exposed to the model as a function tool. */
    public static Map<String, Object> getWeather(
            @Schema(name = "city", description = "The city to look up")
            String city) {
        return Map.of(
            "city", city,
            "forecast", "sunny",
            "temperature_celsius", 21);
    }

    public static void main(String[] args) {
        // Register the OpenTelemetry SDK FIRST. ADK's Telemetry class
        // captures GlobalOpenTelemetry the moment it is loaded, so any
        // ADK class touched before this line traces to a no-op.
        SdkTracerProvider tracerProvider = initializeTracing();

        System.out.println("Arize AX tracing initialized for Google ADK.");

        LlmAgent agent = LlmAgent.builder()
            .name("weather_agent")
            .model("gemini-2.5-flash")
            .description("Answers weather questions with the getWeather tool.")
            .instruction("Always call getWeather before answering. "
                + "Reply in one short sentence.")
            .tools(FunctionTool.create(Main.class, "getWeather"))
            .build();

        InMemoryRunner runner = new InMemoryRunner(agent);
        String userId = "user-123";
        Session session = runner.sessionService()
            .createSession(runner.appName(), userId)
            .blockingGet();

        Content message = Content.fromParts(
            Part.fromText("What is the weather in Paris right now?"));

        runner.runAsync(userId, session.id(), message)
            .blockingForEach(event -> {
                if (event.finalResponse()) {
                    System.out.println("Agent: " + event.stringifyContent());
                }
            });

        // Force flush + shutdown — without this the JVM may exit before
        // the BatchSpanProcessor delivers its queue and spans get dropped.
        tracerProvider.forceFlush().join(10, TimeUnit.SECONDS);
        tracerProvider.shutdown().join(10, TimeUnit.SECONDS);
    }

    private static SdkTracerProvider initializeTracing() {
        String apiKey = System.getenv("ARIZE_API_KEY");
        String spaceId = System.getenv("ARIZE_SPACE_ID");
        String project = System.getenv().getOrDefault(
            "ARIZE_PROJECT_NAME", "google-adk-java-tracing-example");

        // Resource: service name + Arize project name. The latter is what
        // makes the trace land under the right project in Arize AX.
        Resource resource = Resource.getDefault().merge(Resource.create(
            Attributes.of(
                AttributeKey.stringKey("service.name"),
                "google-adk-java-example",
                // openinference.project.name = the literal value of
                // SemanticResourceAttributes.SEMRESATTRS_PROJECT_NAME.
                AttributeKey.stringKey("openinference.project.name"),
                project)));

        OtlpGrpcSpanExporter exporter = OtlpGrpcSpanExporter.builder()
            .setEndpoint("https://otlp.arize.com:443")
            .setHeaders(() -> Map.of(
                "authorization", apiKey,
                "arize-space-id", spaceId,
                "arize-interface", "java"))
            .setTimeout(Duration.ofSeconds(10))
            .build();

        SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
            // BatchSpanProcessor batches spans and exports them
            // asynchronously — the right default for production. Note it
            // has NO static create(); always construct it via
            // .builder(exporter).build().
            .addSpanProcessor(BatchSpanProcessor.builder(exporter)
                .setScheduleDelay(Duration.ofSeconds(1))
                .build())
            .setResource(resource)
            .build();

        OpenTelemetrySdk.builder()
            .setTracerProvider(tracerProvider)
            .setPropagators(ContextPropagators.create(
                W3CTraceContextPropagator.getInstance()))
            .buildAndRegisterGlobal();

        return tracerProvider;
    }
}
```

Note that `Main` never imports an OpenInference class. The agent rewrites ADK's own telemetry methods at JVM startup, so instrumentation is entirely a launch-time concern.

## Run Google ADK

```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
gradle run
```

To launch outside Gradle, download the shaded jar from Maven Central once and pass it to the JVM yourself:

```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
VERSION=0.1.1
ARTIFACT=openinference-instrumentation-adk-java
BASE=https://repo1.maven.org/maven2/com/arize/$ARTIFACT

curl -sSLO "$BASE/$VERSION/$ARTIFACT-$VERSION-all.jar"

java \
  -javaagent:"$ARTIFACT-$VERSION-all.jar" \
  -cp "<your-application-classpath>" \
  example.Main
```

### Expected output

```text wrap theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
[main] INFO com.arize.instrumentation.adk.TelemetryAgent - OpenInference ADK instrumentation installed
Arize AX tracing initialized for Google ADK.
Agent: The weather in Paris is sunny with a temperature of 21 degrees Celsius.
```

## Verify in Arize AX

1. Open your Arize AX space and select project **`google-adk-java-tracing-example`**.
2. You should see a new trace within \~30–60 seconds (Arize's Java OTLP ingest is slightly slower than the Python path) rooted at an `invocation` chain span carrying `session.id`, `user.id`, and `agent.name`, with an `agent_run [weather_agent]` agent span, two `call_llm` LLM spans (the tool-call turn and the final answer, each with `llm.model_name`, `llm.provider`, messages, tool schemas, and token counts), and a `tool_call [getWeather]` tool span holding the tool arguments and result.
3. If no traces appear, see [Troubleshooting](#troubleshooting).

### Check from the skill, CLI, or SDK

Confirm spans are actually reaching your Arize AX project. Use whichever fits your workflow — the skill and CLI work for any framework; the SDK check is shown for each language.

<Tabs>
  <Tab title="Arize skill (agent)">
    Install the [Arize Skills](https://github.com/Arize-ai/arize-skills) plugin and let your coding agent check for you:

    ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
    npx skills add Arize-ai/arize-skills
    ```

    Then prompt your agent:

    > Use the `arize-trace` skill to export and analyze recent traces from my project. Confirm spans are arriving, and summarize any errors or latency issues.
  </Tab>

  <Tab title="AX CLI">
    Export recent spans for your project — any rows mean traces are landing:

    ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
    ax spans export "$ARIZE_PROJECT_NAME" --space "$ARIZE_SPACE_ID" \
      --limit 5 --stdout | jq 'length'
    ```

    A non-zero count confirms spans reached Arize AX. Run `ax auth login` first if you have not authenticated. See the [`ax spans` reference](/docs/api-clients/cli/spans).
  </Tab>

  <Tab title="SDK">
    Query the project's spans and check that at least one came back.

    <CodeGroup>
      ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
      import os
      from arize import ArizeClient

      client = ArizeClient(api_key=os.environ["ARIZE_API_KEY"])
      resp = client.spans.list(
          project=os.environ["ARIZE_PROJECT_NAME"],
          space=os.environ["ARIZE_SPACE_ID"],
          limit=5,
      )
      count = len(resp.spans)
      print(
          f"{count} span(s) found" if count else "No spans yet — recheck setup"
      )
      ```

      ```typescript TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
      // Reads ARIZE_API_KEY from the environment.
      import { listSpans } from "@arizeai/ax-client";

      const { data: spans } = await listSpans({
        project: process.env.ARIZE_PROJECT_NAME!,
        space: process.env.ARIZE_SPACE_ID!,
        limit: 5,
      });
      const count = spans.length;
      console.log(
        count ? `${count} span(s) found` : "No spans yet — recheck setup",
      );
      ```

      ```go Go theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
      client, err := arize.NewClient(
          arize.Config{APIKey: os.Getenv("ARIZE_API_KEY")},
      )
      if err != nil {
          log.Fatal(err)
      }
      resp, err := client.Spans.List(ctx, spans.ListRequest{
          Project: os.Getenv("ARIZE_PROJECT_NAME"),
          Space:   os.Getenv("ARIZE_SPACE_ID"),
          Limit:   5,
      })
      if err != nil {
          log.Fatal(err)
      }
      fmt.Printf("%d span(s) found\n", len(resp.Spans))
      ```
    </CodeGroup>

    SDK span references: [Python](/docs/api-clients/python/version-8/client-resources/spans) · [TypeScript](/docs/api-clients/typescript/version-1/client-resources/spans) · [Go](/docs/api-clients/go/version-2/client-resources/spans).
  </Tab>
</Tabs>

## Troubleshooting

* **No traces in Arize AX.** Confirm `ARIZE_SPACE_ID` and `ARIZE_API_KEY` are set in the same shell that runs `gradle run`. The OTLP exporter logs at `FINE` level — to surface delivery errors, add `java.util.logging.Logger.getLogger("io.opentelemetry").setLevel(Level.FINE)` before initialization, or wire an SLF4J implementation. To confirm spans are being produced locally before troubleshooting export, add `SimpleSpanProcessor.create(LoggingSpanExporter.create())` as an extra processor — it prints every span to stderr.
* **No `OpenInference ADK instrumentation installed` line at startup.** The `-javaagent` never reached the JVM. Under Gradle, check that `tasks.named('run')` sets `jvmArgs` and that `configurations.openinferenceAgent` resolves — `gradle dependencies --configuration openinferenceAgent` should list exactly one jar.
* **Agent installed, but spans have no OpenInference attributes.** You are on an unsupported ADK version. The agent advises `com.google.adk.Telemetry`, which ADK renamed to `com.google.adk.telemetry.Tracing` in `0.6.0`; pin `com.google.adk:google-adk` to `0.5.0` or lower. The warning `OpenInference ADK instrumentation failed to transform ...` in the log confirms a matcher mismatch.
* **Spans appear, but prompts and responses are empty.** Your application touched an ADK class before `buildAndRegisterGlobal()` ran, so ADK captured a no-op `GlobalOpenTelemetry`. Move every ADK construction call after the tracer registration, including static initializers and dependency-injection graphs that build `LlmAgent` eagerly.
* **`401`/`403` from Google.** Verify `GOOGLE_API_KEY` is set and enabled for `gemini-2.5-flash`. If both `GOOGLE_API_KEY` and `GEMINI_API_KEY` are set, ADK logs a warning and uses `GOOGLE_API_KEY`.
* **Spans dropped at JVM exit.** `BatchSpanProcessor` exports asynchronously. Always `tracerProvider.forceFlush().join(...)` and `tracerProvider.shutdown().join(...)` before `main` returns. For a long-running service, register a JVM shutdown hook instead.
* **`SLF4J(W): No SLF4J providers were found.`** Harmless, but it also hides the agent's own install log line. Add `runtimeOnly 'org.slf4j:slf4j-simple:2.0.17'` to `build.gradle` to see it.

## Resources

<CardGroup>
  <Card icon="github" href="https://github.com/google/adk-java" title="Google ADK for Java" horizontal />

  <Card icon="terminal" href="https://central.sonatype.com/artifact/com.arize/openinference-instrumentation-adk-java" title="OpenInference ADK Java Agent (Maven Central)" horizontal />

  <Card icon="github" href="https://github.com/Arize-ai/openinference/tree/main/java/instrumentation/openinference-instrumentation-adk-java" title="OpenInference ADK Java Source" horizontal />

  <Card icon="github" href="https://github.com/Arize-ai/openinference/tree/main/java/examples/adk-java-example" title="Runnable ADK Java Example" horizontal />

  <Card icon="route" href="/docs/ax/integrations/python-agent-frameworks/google-adk/google-adk-tracing" title="Google ADK for Python" horizontal />
</CardGroup>
