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

# Spring AI

> Trace Spring AI Java applications with OpenInference and send spans to Arize AX for LLM observability.

[Spring AI](https://docs.spring.io/spring-ai/reference/) is the Java/Spring port of LangChain-style LLM orchestration — model abstraction, tool calling, structured output, and prompt templates that integrate with Spring's broader ecosystem. Arize AX captures every Spring AI chat-model call via the [`com.arize:openinference-instrumentation-springAI`](https://central.sonatype.com/artifact/com.arize/openinference-instrumentation-springAI) artifact, registered as a Micrometer observation handler.

## Prerequisites

* Java 17+ (Spring AI 1.0+ requires 17)
* An Arize AX account ([sign up](https://arize.com/sign-up/))
* An `OPENAI_API_KEY` from the [OpenAI Platform](https://platform.openai.com/api-keys)

## Launch Arize

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

Add the dependencies to `build.gradle`:

```groovy theme={null}
plugins {
    id 'application'
    id 'io.spring.dependency-management' version '1.1.7'
}

repositories {
    mavenCentral()
}

ext {
    set('springAiVersion', "1.0.0")
}

dependencies {
    // OpenInference Spring AI instrumentor + semantic conventions
    implementation 'com.arize:openinference-instrumentation-springAI:0.1.9'
    implementation 'com.arize:openinference-semantic-conventions:0.1.12'

    // Spring AI OpenAI provider (BOM pulls in the rest)
    implementation 'org.springframework.ai:spring-ai-starter-model-openai'
    implementation 'io.micrometer:micrometer-tracing-bridge-brave:1.5.1'

    // OpenTelemetry SDK + OTLP exporter
    implementation 'io.opentelemetry:opentelemetry-sdk:1.50.0'
    implementation 'io.opentelemetry:opentelemetry-exporter-otlp:1.50.0'
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.ai:spring-ai-bom:${springAiVersion}"
    }
}

application {
    mainClass = 'example.Main'
}
```

<Note>
  Note the artifact name uses **camelCase** — `openinference-instrumentation-springAI`, not `spring-ai`. That's how it's published on Maven Central.
</Note>

## Configure credentials

```bash theme={null}
export ARIZE_SPACE_ID="<your-space-id>"
export ARIZE_API_KEY="<your-api-key>"
export ARIZE_PROJECT_NAME="spring-ai-tracing-example"
export OPENAI_API_KEY="<your-openai-api-key>"
```

## Setup tracing

Spring AI plugs into the Micrometer `ObservationRegistry`. The `SpringAIInstrumentor` is an observation handler that turns each Spring AI call into an OpenInference span. Wire it up in `main`:

```java theme={null}
// src/main/java/example/Main.java
package example;

import com.arize.instrumentation.OITracer;
import com.arize.instrumentation.TraceConfig;
import com.arize.instrumentation.springAI.SpringAIInstrumentor;
import io.micrometer.observation.ObservationRegistry;
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 org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;

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

public class Main {
    public static void main(String[] args) {
        String arizeKey = System.getenv("ARIZE_API_KEY");
        String spaceId  = System.getenv("ARIZE_SPACE_ID");
        String project  = System.getenv().getOrDefault(
            "ARIZE_PROJECT_NAME", "spring-ai-tracing-example");

        Resource resource = Resource.getDefault().merge(Resource.create(
            Attributes.of(
                AttributeKey.stringKey("service.name"), "spring-ai",
                // Literal value of the SEMRESATTRS_PROJECT_NAME constant
                // from com.arize:openinference-semantic-conventions.
                AttributeKey.stringKey("openinference.project.name"),
                project)));

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

        SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
            .addSpanProcessor(BatchSpanProcessor.builder(exporter)
                .setScheduleDelay(Duration.ofSeconds(1))
                .build())
            .setResource(resource)
            .build();

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

        System.out.println("Arize AX tracing initialized for Spring AI.");

        // Wrap the SDK tracer in an OITracer and register the Spring AI
        // instrumentor as an observation handler.
        OITracer tracer = new OITracer(
            tracerProvider.get("com.arize.spring-ai"),
            TraceConfig.getDefault());

        ObservationRegistry registry = ObservationRegistry.create();
        registry.observationConfig()
            .observationHandler(new SpringAIInstrumentor(tracer));

        // Build a Spring AI chat model that reports observations to the
        // registered registry. Reads OPENAI_API_KEY from the environment.
        OpenAiApi openAiApi = OpenAiApi.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .build();

        OpenAiChatOptions options = OpenAiChatOptions.builder()
            .model("gpt-5")
            .build();

        OpenAiChatModel chatModel = OpenAiChatModel.builder()
            .openAiApi(openAiApi)
            .defaultOptions(options)
            .observationRegistry(registry)
            .build();

        ChatResponse response = chatModel.call(new Prompt(
            "Why is the ocean salty? Answer in two sentences."));
        System.out.println(response.getResult().getOutput().getText());

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

## Run Spring AI

```bash theme={null}
gradle run
```

### Expected output

```text wrap theme={null}
Arize AX tracing initialized for Spring AI.
The ocean is salty because rivers continuously dissolve mineral salts from rocks and soil and carry them to the sea, where they accumulate over millions of years. Water leaves the ocean through evaporation but the salts remain, steadily concentrating until reaching today's roughly 3.5% salinity.
```

## Verify in Arize

1. Open your Arize AX space and select project **`spring-ai-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) containing a `generate` LLM span with the prompt, response, model name (`gpt-5`), and token-usage attributes attached.
3. If no traces appear, see [Troubleshooting](#troubleshooting).

## Troubleshooting

* **No traces in Arize.** Confirm `ARIZE_SPACE_ID` and `ARIZE_API_KEY` are set in the same shell that runs `gradle run`. 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 spans, but the request succeeded.** Spring AI only emits observations when the chat model is built with an `observationRegistry`. Make sure your `OpenAiChatModel.builder()` chain includes `.observationRegistry(registry)` and that the registry has the `SpringAIInstrumentor` registered as an observation handler.
* **`401` from OpenAI.** Verify `OPENAI_API_KEY` is set and has access to `gpt-5`. Swap `model("gpt-5")` in `OpenAiChatOptions.builder()` for a model your key can call.
* **Spans dropped at JVM exit.** `BatchSpanProcessor` exports asynchronously. Always `tracerProvider.forceFlush().join(...)` and `tracerProvider.shutdown().join(...)` before `main` returns.
* **Using Spring Boot.** This guide targets a plain Spring AI library use-case (no `@SpringBootApplication`). For a Spring Boot starter pattern with auto-configuration, see [Arconia](/ax/integrations/java/arconia/arconia-tracing) — it bundles Spring AI + OpenInference + OTel auto-config under one dependency.

## Resources

<CardGroup>
  <Card icon="book-open" href="https://docs.spring.io/spring-ai/reference/" title="Spring AI Documentation" horizontal />

  <Card icon="terminal" href="https://central.sonatype.com/artifact/com.arize/openinference-instrumentation-springAI" title="OpenInference Spring AI Instrumentor (Maven Central)" horizontal />

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

  <Card icon="github" href="https://github.com/Arize-ai/openinference/tree/main/java/examples/spring-ai-example" title="Spring AI Example (with tools + multi-turn)" horizontal />
</CardGroup>
