Skip to content
Merged
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "4.30.0"
".": "4.31.0"
}
4 changes: 2 additions & 2 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 151
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-00994178cc8e20d71754b00c54b0e4f5b4128e1c1cce765e9b7d696bd8c80d33.yml
openapi_spec_hash: 81f404053b663f987209b4fb2d08a230
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-dd99495ad509338e6de862802839360dfe394d5cd6d6ba6d13fec8fca92328b8.yml
openapi_spec_hash: 68abda9122013a9ae3f084cfdbe8e8c1
config_hash: 5635033cdc8c930255f8b529a78de722
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
# Changelog

## 4.31.0 (2026-04-08)

Full Changelog: [v4.30.0...v4.31.0](https://github.com/openai/openai-java/compare/v4.30.0...v4.31.0)

### Features

* **api:** add phase field to conversations Message ([e562a17](https://github.com/openai/openai-java/commit/e562a1701cfb7c92aa308d80d7bfe8e99c6394c8))
* **api:** add WEB_SEARCH_CALL_RESULTS to ResponseIncludable enum ([eda0a61](https://github.com/openai/openai-java/commit/eda0a61f837af3a0aba2851a23320aadfce82979))
* **client:** add support for short-lived tokens ([#1185](https://github.com/openai/openai-java/issues/1185)) ([40e729d](https://github.com/openai/openai-java/commit/40e729ddc83f37dfe6429d257072cd39396fb104))


### Bug Fixes

* **api:** remove web_search_call.results from ResponseIncludable ([936b2ab](https://github.com/openai/openai-java/commit/936b2ab08e8641bd1d6a1b2432140762185e22eb))


### Chores

* **internal:** update multipart form array serialization ([240ca42](https://github.com/openai/openai-java/commit/240ca42401366b75cf70b0eb0ff9f63425c9b358))
* **tests:** bump steady to v0.20.1 ([a3c95b0](https://github.com/openai/openai-java/commit/a3c95b073d7b10a13ce83865947ab4fbec5a95d0))
* **tests:** bump steady to v0.20.2 ([78b1d56](https://github.com/openai/openai-java/commit/78b1d5629bf7d31173ec1cec50f3191959cdba9d))


### Documentation

* **api:** clarify file_batches usage in vector stores files and file batches ([9c56841](https://github.com/openai/openai-java/commit/9c56841ff2f052a7e54290b36c3f74528be3de19))
* fix function arguments typo in README ([#713](https://github.com/openai/openai-java/issues/713)) ([36c4888](https://github.com/openai/openai-java/commit/36c48883fa898da1b41ccc03fe1682934079923c))

## 4.30.0 (2026-03-25)

Full Changelog: [v4.29.1...v4.30.0](https://github.com/openai/openai-java/compare/v4.29.1...v4.30.0)
Expand Down
93 changes: 85 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@

<!-- x-release-please-start-version -->

[![Maven Central](https://img.shields.io/maven-central/v/com.openai/openai-java)](https://central.sonatype.com/artifact/com.openai/openai-java/4.30.0)
[![javadoc](https://javadoc.io/badge2/com.openai/openai-java/4.30.0/javadoc.svg)](https://javadoc.io/doc/com.openai/openai-java/4.30.0)
[![Maven Central](https://img.shields.io/maven-central/v/com.openai/openai-java)](https://central.sonatype.com/artifact/com.openai/openai-java/4.31.0)
[![javadoc](https://javadoc.io/badge2/com.openai/openai-java/4.31.0/javadoc.svg)](https://javadoc.io/doc/com.openai/openai-java/4.31.0)

<!-- x-release-please-end -->

The OpenAI Java SDK provides convenient access to the [OpenAI REST API](https://platform.openai.com/docs) from applications written in Java.

<!-- x-release-please-start-version -->

The REST API documentation can be found on [platform.openai.com](https://platform.openai.com/docs). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.openai/openai-java/4.30.0).
The REST API documentation can be found on [platform.openai.com](https://platform.openai.com/docs). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.openai/openai-java/4.31.0).

<!-- x-release-please-end -->

Expand All @@ -24,7 +24,7 @@ The REST API documentation can be found on [platform.openai.com](https://platfor
### Gradle

```kotlin
implementation("com.openai:openai-java:4.30.0")
implementation("com.openai:openai-java:4.31.0")
```

### Maven
Expand All @@ -33,7 +33,7 @@ implementation("com.openai:openai-java:4.30.0")
<dependency>
<groupId>com.openai</groupId>
<artifactId>openai-java</artifactId>
<version>4.30.0</version>
<version>4.31.0</version>
</dependency>
```

Expand Down Expand Up @@ -156,6 +156,83 @@ OpenAIClient clientWithOptions = client.withOptions(optionsBuilder -> {

The `withOptions()` method does not affect the original client or service.

### Workload identity authentication

Workload identity authentication allows applications running in cloud environments (Kubernetes, Azure, GCP) to authenticate using short-lived tokens issued by the cloud provider, instead of long-lived API keys.

#### Basic setup

```java
import com.openai.auth.*;
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;

SubjectTokenProvider provider = K8sServiceAccountTokenProvider.builder().build();

WorkloadIdentity workloadIdentity = WorkloadIdentity.builder()
.clientId("your-client-id")
.identityProviderId("your-identity-provider-id")
.serviceAccountId("your-service-account-id")
.provider(provider)
.build();

OpenAIClient client = OpenAIOkHttpClient.builder()
.workloadIdentity(workloadIdentity)
.build();
```

#### Kubernetes service account token provider

```java
// Use default token path (/var/run/secrets/kubernetes.io/serviceaccount/token)
SubjectTokenProvider provider = K8sServiceAccountTokenProvider.builder().build();
```

```java
// Or specify a custom token path
SubjectTokenProvider provider = K8sServiceAccountTokenProvider.builder()
.tokenPath("/custom/path/to/token")
.build();
```

#### Azure Managed Identity provider

```java
import com.openai.auth.*;

// Use defaults (resource: https://management.azure.com/, api-version: 2018-02-01)
SubjectTokenProvider provider = AzureManagedIdentityTokenProvider.builder()
.build();
```

```java
import com.openai.auth.*;

// Or customize
SubjectTokenProvider provider = AzureManagedIdentityTokenProvider.builder()
.resource("https://management.azure.com/")
.apiVersion("2018-02-01")
.build();
```

#### GCP ID token provider

```java
import com.openai.auth.*;

SubjectTokenProvider provider = GcpIdTokenProvider.builder()
.build();
```

```java
import com.openai.auth.*;

// Or customize the audience
SubjectTokenProvider provider = GcpIdTokenProvider.builder()
.audience("https://api.openai.com/v1")
.build();
```

## Requests and responses

To send a request to the OpenAI API, build an instance of some `Params` class and pass it to the corresponding client method. When the response is received, it will be deserialized into an instance of a Java class.
Expand Down Expand Up @@ -795,7 +872,7 @@ static class GetSdkScore {
```

When your functions are defined, add them to the input parameters using `addTool(Class<T>)` and then
call them if requested to do so in the AI model's response. `Function.argments(Class<T>)` can be
call them if requested to do so in the AI model's response. `Function.arguments(Class<T>)` can be
used to parse a function's parameters in JSON form to an instance of your function-defining class.
The fields of that instance will be set to the values of the parameters to the function call.

Expand Down Expand Up @@ -1342,7 +1419,7 @@ If you're using Spring Boot, then you can use the SDK's [Spring Boot starter](ht
#### Gradle

```kotlin
implementation("com.openai:openai-java-spring-boot-starter:4.30.0")
implementation("com.openai:openai-java-spring-boot-starter:4.31.0")
```

#### Maven
Expand All @@ -1351,7 +1428,7 @@ implementation("com.openai:openai-java-spring-boot-starter:4.30.0")
<dependency>
<groupId>com.openai</groupId>
<artifactId>openai-java-spring-boot-starter</artifactId>
<version>4.30.0</version>
<version>4.31.0</version>
</dependency>
```

Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ repositories {

allprojects {
group = "com.openai"
version = "4.30.0" // x-release-please-version
version = "4.31.0" // x-release-please-version
}

subprojects {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package com.openai.client.okhttp

import com.fasterxml.jackson.databind.json.JsonMapper
import com.openai.auth.WorkloadIdentity
import com.openai.azure.AzureOpenAIServiceVersion
import com.openai.azure.AzureUrlPathMode
import com.openai.client.OpenAIClient
Expand Down Expand Up @@ -277,6 +278,14 @@ class OpenAIOkHttpClient private constructor() {

fun credential(credential: Credential) = apply { clientOptions.credential(credential) }

fun workloadIdentity(workloadIdentity: WorkloadIdentity?) = apply {
clientOptions.workloadIdentity(workloadIdentity)
}

/** Alias for calling [Builder.workloadIdentity] with `workloadIdentity.orElse(null)`. */
fun workloadIdentity(workloadIdentity: Optional<WorkloadIdentity>) =
workloadIdentity(workloadIdentity.getOrNull())

fun azureServiceVersion(azureServiceVersion: AzureOpenAIServiceVersion) = apply {
clientOptions.azureServiceVersion(azureServiceVersion)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package com.openai.client.okhttp

import com.fasterxml.jackson.databind.json.JsonMapper
import com.openai.auth.WorkloadIdentity
import com.openai.azure.AzureOpenAIServiceVersion
import com.openai.azure.AzureUrlPathMode
import com.openai.client.OpenAIClientAsync
Expand Down Expand Up @@ -277,6 +278,14 @@ class OpenAIOkHttpClientAsync private constructor() {

fun credential(credential: Credential) = apply { clientOptions.credential(credential) }

fun workloadIdentity(workloadIdentity: WorkloadIdentity?) = apply {
clientOptions.workloadIdentity(workloadIdentity)
}

/** Alias for calling [Builder.workloadIdentity] with `workloadIdentity.orElse(null)`. */
fun workloadIdentity(workloadIdentity: Optional<WorkloadIdentity>) =
workloadIdentity(workloadIdentity.getOrNull())

fun azureServiceVersion(azureServiceVersion: AzureOpenAIServiceVersion) = apply {
clientOptions.azureServiceVersion(azureServiceVersion)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package com.openai.auth

import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.jacksonTypeRef
import com.openai.core.http.HttpClient
import com.openai.core.http.HttpMethod
import com.openai.core.http.HttpRequest
import com.openai.errors.SubjectTokenProviderException
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionException

private const val DEFAULT_AUDIENCE = "https://management.azure.com/"
private const val DEFAULT_AZURE_API_VERSION = "2018-02-01"
private const val AZURE_IMDS_BASE_URL = "http://169.254.169.254/metadata/identity/oauth2/token"

/**
* A [SubjectTokenProvider] that fetches an identity token from the Azure Instance Metadata Service
* (IMDS).
*
* It calls the local IMDS endpoint and returns the `access_token` from the response.
*/
class AzureManagedIdentityTokenProvider
private constructor(private val resource: String, private val apiVersion: String) :
SubjectTokenProvider {

override fun tokenType(): SubjectTokenType = SubjectTokenType.JWT

override fun getToken(httpClient: HttpClient, jsonMapper: JsonMapper): String {
val request =
HttpRequest.builder()
.method(HttpMethod.GET)
.baseUrl(AZURE_IMDS_BASE_URL)
.putHeader("Metadata", "true")
.putQueryParam("api-version", apiVersion)
.putQueryParam("resource", resource)
.build()

return try {
val response = httpClient.execute(request)
response.use {
if (response.statusCode() != 200) {
throw SubjectTokenProviderException(
provider = "azure-imds",
message = "IMDS returned status ${response.statusCode()}",
)
}

val result =
jsonMapper.readValue(response.body(), jacksonTypeRef<AzureIMDSResponse>())

if (result.accessToken.isEmpty()) {
throw SubjectTokenProviderException(
provider = "azure-imds",
message = "IMDS response missing 'access_token' field",
)
}

result.accessToken
}
} catch (e: SubjectTokenProviderException) {
throw e
} catch (e: Exception) {
throw SubjectTokenProviderException(
provider = "azure-imds",
message = "failed to fetch token from IMDS",
cause = e,
)
}
}

override fun getTokenAsync(
httpClient: HttpClient,
jsonMapper: JsonMapper,
): CompletableFuture<String> {
val request =
HttpRequest.builder()
.method(HttpMethod.GET)
.baseUrl(AZURE_IMDS_BASE_URL)
.putHeader("Metadata", "true")
.putQueryParam("api-version", apiVersion)
.putQueryParam("resource", resource)
.build()

return httpClient
.executeAsync(request)
.thenApply { response ->
response.use {
if (response.statusCode() != 200) {
throw SubjectTokenProviderException(
provider = "azure-imds",
message = "IMDS returned status ${response.statusCode()}",
)
}

val result =
jsonMapper.readValue(response.body(), jacksonTypeRef<AzureIMDSResponse>())

if (result.accessToken.isEmpty()) {
throw SubjectTokenProviderException(
provider = "azure-imds",
message = "IMDS response missing 'access_token' field",
)
}

result.accessToken
}
}
.exceptionally { e ->
val cause = if (e is CompletionException) e.cause ?: e else e
if (cause is SubjectTokenProviderException) throw cause
throw SubjectTokenProviderException(
provider = "azure-imds",
message = "failed to fetch token from IMDS",
cause = cause,
)
}
}

@JsonIgnoreProperties(ignoreUnknown = true)
private data class AzureIMDSResponse(@JsonProperty("access_token") val accessToken: String)

companion object {
@JvmStatic fun builder() = Builder()
}

class Builder internal constructor() {

private var resource: String = DEFAULT_AUDIENCE
private var apiVersion: String = DEFAULT_AZURE_API_VERSION

/**
* The Azure resource URI to request a token for (default: `https://management.azure.com/`).
*/
fun resource(resource: String) = apply { this.resource = resource }

/** The IMDS API version to use (default: `2018-02-01`). */
fun apiVersion(apiVersion: String) = apply { this.apiVersion = apiVersion }

fun build(): AzureManagedIdentityTokenProvider =
AzureManagedIdentityTokenProvider(resource, apiVersion)
}
}
Loading
Loading