Use this file to discover all available pages before exploring further.
LangChain4j is the Java/Kotlin port of LangChain — a framework for composing LLM calls, tools, retrieval, and agents on the JVM. Arize AX captures every LangChain4j chat-model call via the com.arize:openinference-instrumentation-langchain4j artifact, attached as a model listener.
Java doesn’t separate setup from runtime the way Python or TypeScript do — both happen in the same main method. Put the OpenTelemetry SDK initialization at the top, then use it for the rest of the program:
// src/main/java/example/Main.javapackage example;import com.arize.instrumentation.langchain4j.LangChain4jInstrumentor;import com.arize.instrumentation.langchain4j.LangChain4jModelListener;import dev.langchain4j.model.chat.ChatModel;import dev.langchain4j.model.openai.OpenAiChatModel;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.List;import java.util.Map;import java.util.concurrent.TimeUnit;public class Main { public static void main(String[] args) { String apiKey = System.getenv("ARIZE_API_KEY"); String spaceId = System.getenv("ARIZE_SPACE_ID"); String project = System.getenv().getOrDefault( "ARIZE_PROJECT_NAME", "langchain4j-tracing-example"); // Resource: service name + Arize project name (the latter is what // makes the trace appear under the right project in Arize). Resource resource = Resource.getDefault().merge(Resource.create( Attributes.of( AttributeKey.stringKey("service.name"), "langchain4j", // openinference.project.name = the literal value of // SemanticResourceAttributes.SEMRESATTRS_PROJECT_NAME. AttributeKey.stringKey("openinference.project.name"), project))); // OTLP gRPC exporter pointed at Arize. OtlpGrpcSpanExporter exporter = OtlpGrpcSpanExporter.builder() .setEndpoint("https://otlp.arize.com:443") .setHeaders(() -> Map.of( "authorization", apiKey, "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 LangChain4j."); // The instrumentor returns a listener; attach it to every // ChatModel you build. Listeners are NOT auto-registered. LangChain4jInstrumentor instrumentor = LangChain4jInstrumentor.instrument(); LangChain4jModelListener listener = instrumentor.createModelListener(); ChatModel model = OpenAiChatModel.builder() .apiKey(System.getenv("OPENAI_API_KEY")) .modelName("gpt-5") .timeout(Duration.ofMinutes(3)) .listeners(List.of(listener)) .build(); String response = model.chat( "Why is the ocean salty? Answer in two sentences."); System.out.println(response); // 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); }}
Arize AX tracing initialized for LangChain4j.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.
Open your Arize AX space and select project langchain4j-tracing-example.
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.
No traces in Arize. 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 a 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 spans, but LangChain4jInstrumentor.instrument() ran. The instrumentor is not auto-global. Each ChatModel (or EmbeddingModel, StreamingChatModel, etc.) must explicitly attach the listener: .listeners(List.of(instrumentor.createModelListener())). Models built without it produce no spans.
401 from OpenAI. Verify OPENAI_API_KEY is set and has access to gpt-5. Swap modelName("gpt-5") 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.
SLF4J(W): No SLF4J providers were found. Harmless — the OpenAI HTTP client looks for an SLF4J implementation. Add implementation 'org.slf4j:slf4j-simple:2.0.9' to build.gradle to silence it (and get HTTP request logs).