Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,8 @@ build/
### Mac OS ###
.DS_Store

### JBang ###
.jbang/

### Environment ###
.env
20 changes: 2 additions & 18 deletions .mvn/wrapper/maven-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,19 +1,3 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
wrapperVersion=3.3.2
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.15/apache-maven-3.9.15-bin.zip
237 changes: 157 additions & 80 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,120 +1,197 @@
# langfuse-java
# Langfuse Java SDK

This repository contains an auto-generated Langfuse API client for Java based on our [API specification](https://github.com/langfuse/langfuse/tree/main/fern/apis/server).
See the [Langfuse API reference](https://api.reference.langfuse.com) for more details on the available endpoints.
Java SDK for [Langfuse](https://langfuse.com) -- the open-source LLM engineering platform for tracing, evaluation, prompt management, and metrics.

**Note:** We recommend to solve tracing via the [OpenTelemetry Instrumentation](https://langfuse.com/docs/opentelemetry/get-started) instead of using the Ingestion API directly. You can use the [OpenTelemetry Java SDK](https://github.com/open-telemetry/opentelemetry-java) and export spans to the [Langfuse OTel endpoint](https://langfuse.com/integrations/native/opentelemetry).
This allows for a more detailed and standardized tracing experience without the need to handle batching and updates internally.
Check out our [Spring AI Example](https://langfuse.com/docs/integrations/spring-ai) for more details.
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Installation
## Modules

The recommended way to install the langfuse-java API client is via Maven Central:
| Module | Artifact | Description |
|--------|----------|-------------|
| [langfuse-java-api](langfuse-java-api/) | `com.langfuse:langfuse-java-api` | API interfaces, generated model types, and SPI |
| [langfuse-java-client](langfuse-java-client/) | `com.langfuse:langfuse-java-client` | Reference HTTP client (Jackson 2 & 3) |
| [langfuse-java-testcontainers](langfuse-java-testcontainers/) | `com.langfuse:langfuse-java-testcontainers` | Testcontainers support for integration testing |
| [langfuse-java-legacy](langfuse-java-legacy/) | `com.langfuse:langfuse-java` | Legacy fern-generated SDK (maintained for backward compatibility) |

- **[langfuse-java-api](langfuse-java-api/)** defines the public API contract -- interfaces, generated model types from the [OpenAPI spec](https://cloud.langfuse.com/generated/api/openapi.yml), request objects with builders, and the `ServiceLoader`-based SPI for pluggable client implementations.
- **[langfuse-java-client](langfuse-java-client/)** is the reference HTTP client built on `java.net.http.HttpClient`. It supports both Jackson 2 and Jackson 3, request/response logging with sensitive header masking, and automatic HTTP/1.1 fallback for plain HTTP connections.
- **[langfuse-java-testcontainers](langfuse-java-testcontainers/)** provides `LangfuseContainer` for spinning up a complete Langfuse environment (PostgreSQL, ClickHouse, Redis, MinIO, web server, and worker) in integration tests via [Testcontainers](https://testcontainers.com).
- **[langfuse-java-legacy](langfuse-java-legacy/)** is the original fern-generated SDK, preserved for backward compatibility. New projects should use `langfuse-java-client` instead. See its [README](langfuse-java-legacy/README.md) for usage.

## Design

The Langfuse upstream project uses [Fern](https://buildwithfern.com/) to generate its SDKs. Fern produces an [OpenAPI specification](https://cloud.langfuse.com/generated/api/openapi.yml) but its generated Java code is not customizable -- it lacks builder patterns, dual Jackson support, request parameter objects, and JPMS modules.

This SDK takes a different approach: it uses the Fern-generated OpenAPI spec as input to [openapi-generator](https://openapi-generator.tech/) with [custom Mustache templates](https://openapi-generator.tech/docs/templating) that produce the API interfaces, model types, and client implementations at build time. Almost all Java source in the `langfuse-java-api` and `langfuse-java-client` modules are generated during `mvn compile` -- only a handful of hand-coded classes provide the SPI wiring, Jackson version abstraction, and builder infrastructure.

Because everything is generated at build time, the generated API interfaces, model types, and client implementations are never checked into version control -- only the OpenAPI spec, custom Mustache templates, and hand-coded infrastructure classes are stored in the repository.

This means:
- Updating to a new Langfuse API version is a matter of dropping in the updated `openapi.yml` and rebuilding.
- The generated code includes builders, bean validation annotations, request parameter objects, dual Jackson 2/3 annotations, and `@JsonInclude(NON_EMPTY)` -- none of which the Fern-generated SDK provides.
- Framework integrations (Spring, Quarkus) can provide their own `LangfuseApiBuilderFactory` via `ServiceLoader` without depending on the reference client.
- The `langfuse-java-client` module includes a comprehensive integration test suite (sync and async) that uses the `langfuse-java-testcontainers` module to verify every API operation against a real Langfuse environment.

## Requirements

- Java 17+
- Maven 3.8.1+

## Quick Start

Add the client dependency to your project:

```xml
<dependency>
<groupId>com.langfuse</groupId>
<artifactId>langfuse-java</artifactId>
<version>0.2.0</version>
<artifactId>langfuse-java-client</artifactId>
<version>0.2.1-SNAPSHOT</version>
</dependency>
```

## Usage
You also need a Jackson implementation on the classpath. The SDK supports both Jackson 2 and Jackson 3:

```xml
<!-- Jackson 2 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson2.version}</version>
</dependency>

<!-- OR Jackson 3 -->
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson3.version}</version>
</dependency>
```

Instantiate the Langfuse Client with the respective endpoint and your API Keys.
### Create a client

```java
import com.langfuse.client.LangfuseClient;

LangfuseClient client = LangfuseClient.builder()
.url("https://cloud.langfuse.com") // 🇪🇺 EU data region
// .url("https://us.cloud.langfuse.com") // 🇺🇸 US data region
// .url("http://localhost:3000") // 🏠 Local deployment
.credentials("pk-lf-...", "sk-lf-...")
var langfuse = LangfuseApi.builder()
.username("pk-lf-...") // Langfuse public key
.password("sk-lf-...") // Langfuse secret key
.url("https://cloud.langfuse.com")
.build();
```

An async client is also available via `AsyncLangfuseClient.builder()` with the same configuration options.
The builder uses `ServiceLoader` to discover the client implementation on the classpath.
When both Jackson 2 and Jackson 3 are present, Jackson 3 is preferred.

Make requests using the clients:
### Ingest a trace

```java
import com.langfuse.client.core.LangfuseClientApiException;
import com.langfuse.client.resources.prompts.types.PromptMetaListResponse;

try {
PromptMetaListResponse prompts = client.prompts().list();
} catch (LangfuseClientApiException error) {
System.out.println(error.body());
System.out.println(error.statusCode());
}
var response = langfuse.ingestion().ingestionBatch(
IngestionApi.APIIngestionBatchRequest.newBuilder()
.ingestionBatchRequest(IngestionBatchRequest.builder()
.batch(List.of(new IngestionEvent(IngestionEventOneOf.builder()
.id(UUID.randomUUID().toString())
.timestamp(OffsetDateTime.now().toString())
.type(IngestionEventOneOf.TypeEnum.TRACE_CREATE)
.body(TraceBody.builder()
.id(UUID.randomUUID().toString())
.name("my-trace")
.userId("user-123")
.build())
.build())))
.build())
.build());
```

## Testing
### Query traces

### Unit tests
```java
var traces = langfuse.trace().traceList(
TraceApi.APITraceListRequest.newBuilder()
.name("my-trace")
.limit(10)
.build());

traces.getData().forEach(trace ->
System.out.println(trace.getId() + ": " + trace.getName()));
```

Unit tests (deserialization, query string mapping) run without any credentials:
### Check health

```bash
mvn test
```java
var health = langfuse.health().healthHealth();
System.out.println("Status: " + health.getStatus()); // OK
System.out.println("Version: " + health.getVersion()); // 3.x.x
```

### Integration tests
### Async API

Integration tests connect to a real Langfuse project. They require credentials and are excluded from `mvn test`.
Every synchronous API has an async counterpart that returns `CompletionStage`:

1. Copy `.env.example` to `.env` and fill in your API keys:
```bash
cp .env.example .env
```
```java
langfuse.asyncHealth().healthHealth()
.thenAccept(health -> System.out.println("Status: " + health.getStatus()));
```

### Request/Response logging

```java
var langfuse = LangfuseApi.builder()
.username("pk-lf-...")
.password("sk-lf-...")
.url("https://cloud.langfuse.com")
.logRequests()
.logResponses()
.prettyPrint()
.build();
```

Sensitive headers (e.g. `Authorization`) are automatically masked in log output.

## Integration Testing with Testcontainers

Add the testcontainers module to your test dependencies:

```xml
<dependency>
<groupId>com.langfuse</groupId>
<artifactId>langfuse-java-testcontainers</artifactId>
<version>0.2.1-SNAPSHOT</version>
<scope>test</scope>
</dependency>
```

2. Ensure your Langfuse project contains the following prompts:
- `test-chat-prompt` — chat type, at least one message with `role` and `content`
- `test-text-prompt` — text type, non-empty prompt text
Start a full Langfuse environment (PostgreSQL, ClickHouse, Redis, MinIO, Langfuse web + worker):

3. Run all tests (unit + integration):
```bash
mvn verify
```
```java
var langfuse = new LangfuseContainer();
langfuse.start();

Or run only integration tests:
```bash
mvn failsafe:integration-test
```
var client = LangfuseApi.builder()
.username(langfuse.getPublicKey())
.password(langfuse.getSecretKey())
.url(langfuse.getLangfuseUrl())
.build();
```

Integration tests skip gracefully when credentials are absent.
For sharing a single container across test classes, use the
[Testcontainers singleton pattern](https://testcontainers.com/guides/testcontainers-container-lifecycle/#_using_singleton_containers):

## Drafting a Release
```java
abstract class AbstractIntegrationTest {
static LangfuseContainer langfuse = new LangfuseContainer();

Run `./mvnw release:prepare -DreleaseVersion=` with the version you want to create.
Push the changes including the tag.
static {
langfuse.start();
}
}
```

## Publishing to Maven Central
See the [testcontainers module README](langfuse-java-testcontainers/) for configuration options.

This project is configured to publish to Maven Central.
To publish to Maven Central, you need to configure the following secrets in your GitHub repository:
## Building

- `OSSRH_USERNAME`: Your Sonatype OSSRH username
- `OSSRH_PASSWORD`: Your Sonatype OSSRH password
- `GPG_PRIVATE_KEY`: Your GPG private key for signing artifacts
- `GPG_PASSPHRASE`: The passphrase for your GPG private key
```bash
./mvnw clean verify
```

## Updating
## License

1. Ensure that langfuse-java is placed in the same directory as the main [langfuse](https://github.com/langfuse/langfuse) repository.
2. Setup a new Java fern generator using
```yaml
- name: fernapi/fern-java-sdk
version: 3.38.1
output:
location: local-file-system
path: ../../../../langfuse-java/src/main/java/com/langfuse/client/
config:
client-class-name: LangfuseClient
```
3. Generate the new client code using `npx fern-api generate --api server`.
4. Manually set the `package` across all files to `com.langfuse.client`.
5. Verify that `LangfuseClientBuilder.setAuthentication()` uses `Basic` auth (not `Bearer`).
6. Adjust Javadoc strings with HTML properties as the apidocs package does not support them.
7. Commit the changes in langfuse-java and push them to the repository.
[MIT](LICENSE)
Loading