java/index.md +0 −1937 deleted
File Deleted View Diff
1# OpenAI Java API Library
2
3<!-- x-release-please-start-version -->
4
5[](https://central.sonatype.com/artifact/com.openai/openai-java/4.36.0)
6[](https://javadoc.io/doc/com.openai/openai-java/4.36.0)
7
8<!-- x-release-please-end -->
9
10The OpenAI Java SDK provides convenient access to the [OpenAI REST API](https://platform.openai.com/docs) from applications written in Java.
11
12<!-- x-release-please-start-version -->
13
14The 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.36.0).
15
16<!-- x-release-please-end -->
17
18## Installation
19
20<!-- x-release-please-start-version -->
21
22[_Try `openai-java-spring-boot-starter` if you're using Spring Boot!_](#spring-boot)
23
24### Gradle
25
26```kotlin
27implementation("com.openai:openai-java:4.36.0")
28```
29
30### Maven
31
32```xml
33<dependency>
34 <groupId>com.openai</groupId>
35 <artifactId>openai-java</artifactId>
36 <version>4.36.0</version>
37</dependency>
38```
39
40<!-- x-release-please-end -->
41
42## Requirements
43
44This library requires Java 8 or later.
45
46## Usage
47
48> [!TIP]
49> See the [`openai-java-example`](openai-java-example/src/main/java/com/openai/example) directory for complete and runnable examples!
50
51The primary API for interacting with OpenAI models is the [Responses API](https://platform.openai.com/docs/api-reference/responses). You can generate text from the model with the code below.
52
53```java
54import com.openai.client.OpenAIClient;
55import com.openai.client.okhttp.OpenAIOkHttpClient;
56import com.openai.models.ChatModel;
57import com.openai.models.responses.Response;
58import com.openai.models.responses.ResponseCreateParams;
59
60// Configures using the `OPENAI_API_KEY`, `OPENAI_ORG_ID` and `OPENAI_PROJECT_ID` environment variables
61OpenAIClient client = OpenAIOkHttpClient.fromEnv();
62
63ResponseCreateParams params = ResponseCreateParams.builder()
64 .input("Say this is a test")
65 .model(ChatModel.GPT_5_2)
66 .build();
67Response response = client.responses().create(params);
68```
69
70The previous standard (supported indefinitely) for generating text is the [Chat Completions API](https://platform.openai.com/docs/api-reference/chat). You can use that API to generate text from the model with the code below.
71
72```java
73import com.openai.client.OpenAIClient;
74import com.openai.client.okhttp.OpenAIOkHttpClient;
75import com.openai.models.ChatModel;
76import com.openai.models.chat.completions.ChatCompletion;
77import com.openai.models.chat.completions.ChatCompletionCreateParams;
78
79// Configures using the `openai.apiKey`, `openai.adminKey`, `openai.orgId`, `openai.projectId`, `openai.webhookSecret` and `openai.baseUrl` system properties
80// Or configures using the `OPENAI_API_KEY`, `OPENAI_ADMIN_KEY`, `OPENAI_ORG_ID`, `OPENAI_PROJECT_ID`, `OPENAI_WEBHOOK_SECRET` and `OPENAI_BASE_URL` environment variables
81OpenAIClient client = OpenAIOkHttpClient.fromEnv();
82
83ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
84 .addUserMessage("Say this is a test")
85 .model(ChatModel.GPT_5_2)
86 .build();
87ChatCompletion chatCompletion = client.chat().completions().create(params);
88```
89
90## Client configuration
91
92Configure the client using system properties or environment variables:
93
94```java
95import com.openai.client.OpenAIClient;
96import com.openai.client.okhttp.OpenAIOkHttpClient;
97
98// Configures using the `openai.apiKey`, `openai.adminKey`, `openai.orgId`, `openai.projectId`, `openai.webhookSecret` and `openai.baseUrl` system properties
99// Or configures using the `OPENAI_API_KEY`, `OPENAI_ADMIN_KEY`, `OPENAI_ORG_ID`, `OPENAI_PROJECT_ID`, `OPENAI_WEBHOOK_SECRET` and `OPENAI_BASE_URL` environment variables
100OpenAIClient client = OpenAIOkHttpClient.fromEnv();
101```
102
103Or manually:
104
105```java
106import com.openai.client.OpenAIClient;
107import com.openai.client.okhttp.OpenAIOkHttpClient;
108
109OpenAIClient client = OpenAIOkHttpClient.builder()
110 .apiKey("My API Key")
111 .adminApiKey("My Admin API Key")
112 .build();
113```
114
115Or using a combination of the two approaches:
116
117```java
118import com.openai.client.OpenAIClient;
119import com.openai.client.okhttp.OpenAIOkHttpClient;
120
121OpenAIClient client = OpenAIOkHttpClient.builder()
122 // Configures using the `openai.apiKey`, `openai.adminKey`, `openai.orgId`, `openai.projectId`, `openai.webhookSecret` and `openai.baseUrl` system properties
123 // Or configures using the `OPENAI_API_KEY`, `OPENAI_ADMIN_KEY`, `OPENAI_ORG_ID`, `OPENAI_PROJECT_ID`, `OPENAI_WEBHOOK_SECRET` and `OPENAI_BASE_URL` environment variables
124 .fromEnv()
125 .apiKey("My API Key")
126 .build();
127```
128
129See this table for the available options:
130
131| Setter | System property | Environment variable | Required | Default value |
132| --------------- | ---------------------- | ----------------------- | -------- | ----------------------------- |
133| `apiKey` | `openai.apiKey` | `OPENAI_API_KEY` | false | - |
134| `adminApiKey` | `openai.adminKey` | `OPENAI_ADMIN_KEY` | false | - |
135| `organization` | `openai.orgId` | `OPENAI_ORG_ID` | false | - |
136| `project` | `openai.projectId` | `OPENAI_PROJECT_ID` | false | - |
137| `webhookSecret` | `openai.webhookSecret` | `OPENAI_WEBHOOK_SECRET` | false | - |
138| `baseUrl` | `openai.baseUrl` | `OPENAI_BASE_URL` | true | `"https://api.openai.com/v1"` |
139
140System properties take precedence over environment variables.
141
142> [!TIP]
143> Don't create more than one client in the same application. Each client has a connection pool and
144> thread pools, which are more efficient to share between requests.
145
146### Modifying configuration
147
148To temporarily use a modified client configuration, while reusing the same connection and thread pools, call `withOptions()` on any client or service:
149
150```java
151import com.openai.client.OpenAIClient;
152
153OpenAIClient clientWithOptions = client.withOptions(optionsBuilder -> {
154 optionsBuilder.baseUrl("https://example.com");
155 optionsBuilder.maxRetries(42);
156});
157```
158
159The `withOptions()` method does not affect the original client or service.
160
161### Workload identity authentication
162
163Workload 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.
164
165#### Basic setup
166
167```java
168import com.openai.auth.*;
169import com.openai.client.OpenAIClient;
170import com.openai.client.okhttp.OpenAIOkHttpClient;
171
172SubjectTokenProvider provider = K8sServiceAccountTokenProvider.builder().build();
173
174WorkloadIdentity workloadIdentity = WorkloadIdentity.builder()
175 .identityProviderId("your-identity-provider-id")
176 .serviceAccountId("your-service-account-id")
177 .provider(provider)
178 .build();
179
180OpenAIClient client = OpenAIOkHttpClient.builder()
181 .workloadIdentity(workloadIdentity)
182 .build();
183```
184
185#### Kubernetes service account token provider
186
187```java
188// Use default token path (/var/run/secrets/kubernetes.io/serviceaccount/token)
189SubjectTokenProvider provider = K8sServiceAccountTokenProvider.builder().build();
190```
191
192```java
193// Or specify a custom token path
194SubjectTokenProvider provider = K8sServiceAccountTokenProvider.builder()
195 .tokenPath("/custom/path/to/token")
196 .build();
197```
198
199#### Azure Managed Identity provider
200
201```java
202import com.openai.auth.*;
203
204// Use defaults (resource: https://management.azure.com/, api-version: 2018-02-01)
205SubjectTokenProvider provider = AzureManagedIdentityTokenProvider.builder()
206 .build();
207```
208
209```java
210import com.openai.auth.*;
211
212// Or customize
213SubjectTokenProvider provider = AzureManagedIdentityTokenProvider.builder()
214 .resource("https://management.azure.com/")
215 .apiVersion("2018-02-01")
216 .build();
217```
218
219#### GCP ID token provider
220
221```java
222import com.openai.auth.*;
223
224SubjectTokenProvider provider = GcpIdTokenProvider.builder()
225 .build();
226```
227
228```java
229import com.openai.auth.*;
230
231// Or customize the audience
232SubjectTokenProvider provider = GcpIdTokenProvider.builder()
233 .audience("https://api.openai.com/v1")
234 .build();
235```
236
237## Requests and responses
238
239To 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.
240
241For example, `client.chat().completions().create(...)` should be called with an instance of `ChatCompletionCreateParams`, and it will return an instance of `ChatCompletion`.
242
243## Immutability
244
245Each class in the SDK has an associated [builder](https://blogs.oracle.com/javamagazine/post/exploring-joshua-blochs-builder-design-pattern-in-java) or factory method for constructing it.
246
247Each class is [immutable](https://docs.oracle.com/javase/tutorial/essential/concurrency/immutable.html) once constructed. If the class has an associated builder, then it has a `toBuilder()` method, which can be used to convert it back to a builder for making a modified copy.
248
249Because each class is immutable, builder modification will _never_ affect already built class instances.
250
251## Asynchronous execution
252
253The default client is synchronous. To switch to asynchronous execution, call the `async()` method:
254
255```java
256import com.openai.client.OpenAIClient;
257import com.openai.client.okhttp.OpenAIOkHttpClient;
258import com.openai.models.ChatModel;
259import com.openai.models.chat.completions.ChatCompletion;
260import com.openai.models.chat.completions.ChatCompletionCreateParams;
261import java.util.concurrent.CompletableFuture;
262
263// Configures using the `openai.apiKey`, `openai.adminKey`, `openai.orgId`, `openai.projectId`, `openai.webhookSecret` and `openai.baseUrl` system properties
264// Or configures using the `OPENAI_API_KEY`, `OPENAI_ADMIN_KEY`, `OPENAI_ORG_ID`, `OPENAI_PROJECT_ID`, `OPENAI_WEBHOOK_SECRET` and `OPENAI_BASE_URL` environment variables
265OpenAIClient client = OpenAIOkHttpClient.fromEnv();
266
267ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
268 .addUserMessage("Say this is a test")
269 .model(ChatModel.GPT_5_2)
270 .build();
271CompletableFuture<ChatCompletion> chatCompletion = client.async().chat().completions().create(params);
272```
273
274Or create an asynchronous client from the beginning:
275
276```java
277import com.openai.client.OpenAIClientAsync;
278import com.openai.client.okhttp.OpenAIOkHttpClientAsync;
279import com.openai.models.ChatModel;
280import com.openai.models.chat.completions.ChatCompletion;
281import com.openai.models.chat.completions.ChatCompletionCreateParams;
282import java.util.concurrent.CompletableFuture;
283
284// Configures using the `openai.apiKey`, `openai.adminKey`, `openai.orgId`, `openai.projectId`, `openai.webhookSecret` and `openai.baseUrl` system properties
285// Or configures using the `OPENAI_API_KEY`, `OPENAI_ADMIN_KEY`, `OPENAI_ORG_ID`, `OPENAI_PROJECT_ID`, `OPENAI_WEBHOOK_SECRET` and `OPENAI_BASE_URL` environment variables
286OpenAIClientAsync client = OpenAIOkHttpClientAsync.fromEnv();
287
288ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
289 .addUserMessage("Say this is a test")
290 .model(ChatModel.GPT_5_2)
291 .build();
292CompletableFuture<ChatCompletion> chatCompletion = client.chat().completions().create(params);
293```
294
295The asynchronous client supports the same options as the synchronous one, except most methods return `CompletableFuture`s.
296
297## Streaming
298
299The SDK defines methods that return response "chunk" streams, where each chunk can be individually processed as soon as it arrives instead of waiting on the full response. Streaming methods generally correspond to [SSE](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) or [JSONL](https://jsonlines.org) responses.
300
301Some of these methods may have streaming and non-streaming variants, but a streaming method will always have a `Streaming` suffix in its name, even if it doesn't have a non-streaming variant.
302
303These streaming methods return [`StreamResponse`](openai-java-core/src/main/kotlin/com/openai/core/http/StreamResponse.kt) for synchronous clients:
304
305```java
306import com.openai.core.http.StreamResponse;
307import com.openai.models.chat.completions.ChatCompletionChunk;
308
309try (StreamResponse<ChatCompletionChunk> streamResponse = client.chat().completions().createStreaming(params)) {
310 streamResponse.stream().forEach(chunk -> {
311 System.out.println(chunk);
312 });
313 System.out.println("No more chunks!");
314}
315```
316
317Or [`AsyncStreamResponse`](openai-java-core/src/main/kotlin/com/openai/core/http/AsyncStreamResponse.kt) for asynchronous clients:
318
319```java
320import com.openai.core.http.AsyncStreamResponse;
321import com.openai.models.chat.completions.ChatCompletionChunk;
322import java.util.Optional;
323
324client.async().chat().completions().createStreaming(params).subscribe(chunk -> {
325 System.out.println(chunk);
326});
327
328// If you need to handle errors or completion of the stream
329client.async().chat().completions().createStreaming(params).subscribe(new AsyncStreamResponse.Handler<>() {
330 @Override
331 public void onNext(ChatCompletionChunk chunk) {
332 System.out.println(chunk);
333 }
334
335 @Override
336 public void onComplete(Optional<Throwable> error) {
337 if (error.isPresent()) {
338 System.out.println("Something went wrong!");
339 throw new RuntimeException(error.get());
340 } else {
341 System.out.println("No more chunks!");
342 }
343 }
344});
345
346// Or use futures
347client.async().chat().completions().createStreaming(params)
348 .subscribe(chunk -> {
349 System.out.println(chunk);
350 })
351 .onCompleteFuture()
352 .whenComplete((unused, error) -> {
353 if (error != null) {
354 System.out.println("Something went wrong!");
355 throw new RuntimeException(error);
356 } else {
357 System.out.println("No more chunks!");
358 }
359 });
360```
361
362Async streaming uses a dedicated per-client cached thread pool [`Executor`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html) to stream without blocking the current thread. This default is suitable for most purposes.
363
364To use a different `Executor`, configure the subscription using the `executor` parameter:
365
366```java
367import java.util.concurrent.Executor;
368import java.util.concurrent.Executors;
369
370Executor executor = Executors.newFixedThreadPool(4);
371client.async().chat().completions().createStreaming(params).subscribe(
372 chunk -> System.out.println(chunk), executor
373);
374```
375
376Or configure the client globally using the `streamHandlerExecutor` method:
377
378```java
379import com.openai.client.OpenAIClient;
380import com.openai.client.okhttp.OpenAIOkHttpClient;
381import java.util.concurrent.Executors;
382
383OpenAIClient client = OpenAIOkHttpClient.builder()
384 .fromEnv()
385 .streamHandlerExecutor(Executors.newFixedThreadPool(4))
386 .build();
387```
388
389### Streaming helpers
390
391The SDK provides conveniences for streamed chat completions. A
392[`ChatCompletionAccumulator`](openai-java-core/src/main/kotlin/com/openai/helpers/ChatCompletionAccumulator.kt)
393can record the stream of chat completion chunks in the response as they are processed and accumulate
394a [`ChatCompletion`](openai-java-core/src/main/kotlin/com/openai/models/chat/completions/ChatCompletion.kt)
395object similar to that which would have been returned by the non-streaming API.
396
397For a synchronous response add a
398[`Stream.peek()`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#peek-java.util.function.Consumer-)
399call to the stream pipeline to accumulate each chunk:
400
401```java
402import com.openai.core.http.StreamResponse;
403import com.openai.helpers.ChatCompletionAccumulator;
404import com.openai.models.chat.completions.ChatCompletion;
405import com.openai.models.chat.completions.ChatCompletionChunk;
406
407ChatCompletionAccumulator chatCompletionAccumulator = ChatCompletionAccumulator.create();
408
409try (StreamResponse<ChatCompletionChunk> streamResponse =
410 client.chat().completions().createStreaming(createParams)) {
411 streamResponse.stream()
412 .peek(chatCompletionAccumulator::accumulate)
413 .flatMap(completion -> completion.choices().stream())
414 .flatMap(choice -> choice.delta().content().stream())
415 .forEach(System.out::print);
416}
417
418ChatCompletion chatCompletion = chatCompletionAccumulator.chatCompletion();
419```
420
421For an asynchronous response, add the `ChatCompletionAccumulator` to the `subscribe()` call:
422
423```java
424import com.openai.helpers.ChatCompletionAccumulator;
425import com.openai.models.chat.completions.ChatCompletion;
426
427ChatCompletionAccumulator chatCompletionAccumulator = ChatCompletionAccumulator.create();
428
429client.chat()
430 .completions()
431 .createStreaming(createParams)
432 .subscribe(chunk -> chatCompletionAccumulator.accumulate(chunk).choices().stream()
433 .flatMap(choice -> choice.delta().content().stream())
434 .forEach(System.out::print))
435 .onCompleteFuture()
436 .join();
437
438ChatCompletion chatCompletion = chatCompletionAccumulator.chatCompletion();
439```
440
441The SDK provides conveniences for streamed responses. A
442[`ResponseAccumulator`](openai-java-core/src/main/kotlin/com/openai/helpers/ResponseAccumulator.kt)
443can record the stream of response events as they are processed and accumulate a
444[`Response`](openai-java-core/src/main/kotlin/com/openai/models/responses/Response.kt)
445object similar to that which would have been returned by the non-streaming API.
446
447For a synchronous response add a
448[`Stream.peek()`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#peek-java.util.function.Consumer-)
449call to the stream pipeline to accumulate each event:
450
451```java
452import com.openai.core.http.StreamResponse;
453import com.openai.helpers.ResponseAccumulator;
454import com.openai.models.responses.Response;
455import com.openai.models.responses.ResponseStreamEvent;
456
457ResponseAccumulator responseAccumulator = ResponseAccumulator.create();
458
459try (StreamResponse<ResponseStreamEvent> streamResponse =
460 client.responses().createStreaming(createParams)) {
461 streamResponse.stream()
462 .peek(responseAccumulator::accumulate)
463 .flatMap(event -> event.outputTextDelta().stream())
464 .forEach(textEvent -> System.out.print(textEvent.delta()));
465}
466
467Response response = responseAccumulator.response();
468```
469
470For an asynchronous response, add the `ResponseAccumulator` to the `subscribe()` call:
471
472```java
473import com.openai.helpers.ResponseAccumulator;
474import com.openai.models.responses.Response;
475
476ResponseAccumulator responseAccumulator = ResponseAccumulator.create();
477
478client.responses()
479 .createStreaming(createParams)
480 .subscribe(event -> responseAccumulator.accumulate(event)
481 .outputTextDelta().ifPresent(textEvent -> System.out.print(textEvent.delta())))
482 .onCompleteFuture()
483 .join();
484
485Response response = responseAccumulator.response();
486```
487
488## Structured outputs with JSON schemas
489
490Open AI [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs?api-mode=chat)
491is a feature that ensures that the model will always generate responses that adhere to a supplied
492[JSON schema](https://json-schema.org/overview/what-is-jsonschema).
493
494A JSON schema can be defined by creating a
495[`ResponseFormatJsonSchema`](openai-java-core/src/main/kotlin/com/openai/models/ResponseFormatJsonSchema.kt)
496and setting it on the input parameters. However, for greater convenience, a JSON schema can instead
497be derived automatically from the structure of an arbitrary Java class. The JSON content from the
498response will then be converted automatically to an instance of that Java class. A full, working
499example of the use of Structured Outputs with arbitrary Java classes can be seen in
500[`StructuredOutputsExample`](openai-java-example/src/main/java/com/openai/example/StructuredOutputsExample.java).
501
502Java classes can contain fields declared to be instances of other classes and can use collections
503(see [Defining JSON schema properties](#defining-json-schema-properties) for more details):
504
505```java
506class Person {
507 public String name;
508 public int birthYear;
509}
510
511class Book {
512 public String title;
513 public Person author;
514 public int publicationYear;
515}
516
517class BookList {
518 public List<Book> books;
519}
520```
521
522Pass the top-level class—`BookList` in this example—to `responseFormat(Class<T>)` when building the
523parameters and then access an instance of `BookList` from the generated message content in the
524response:
525
526```java
527import com.openai.models.ChatModel;
528import com.openai.models.chat.completions.ChatCompletionCreateParams;
529import com.openai.models.chat.completions.StructuredChatCompletionCreateParams;
530
531StructuredChatCompletionCreateParams<BookList> params = ChatCompletionCreateParams.builder()
532 .addUserMessage("List some famous late twentieth century novels.")
533 .model(ChatModel.GPT_5_2)
534 .responseFormat(BookList.class)
535 .build();
536
537client.chat().completions().create(params).choices().stream()
538 .flatMap(choice -> choice.message().content().stream())
539 .flatMap(bookList -> bookList.books.stream())
540 .forEach(book -> System.out.println(book.title + " by " + book.author.name));
541```
542
543You can start building the parameters with an instance of
544[`ChatCompletionCreateParams.Builder`](openai-java-core/src/main/kotlin/com/openai/models/chat/completions/ChatCompletionCreateParams.kt)
545or
546[`StructuredChatCompletionCreateParams.Builder`](openai-java-core/src/main/kotlin/com/openai/models/chat/completions/StructuredChatCompletionCreateParams.kt).
547If you start with the former (which allows for more compact code) the builder type will change to
548the latter when `ChatCompletionCreateParams.Builder.responseFormat(Class<T>)` is called.
549
550If a field in a class is optional and does not require a defined value, you can represent this using
551the [`java.util.Optional`](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html) class.
552It is up to the AI model to decide whether to provide a value for that field or leave it empty.
553
554```java
555import java.util.Optional;
556
557class Book {
558 public String title;
559 public Person author;
560 public int publicationYear;
561 public Optional<String> isbn;
562}
563```
564
565Generic type information for fields is retained in the class's metadata, but _generic type erasure_
566applies in other scopes. While, for example, a JSON schema defining an array of books can be derived
567from the `BookList.books` field with type `List<Book>`, a valid JSON schema cannot be derived from a
568local variable of that same type, so the following will _not_ work:
569
570```java
571List<Book> books = new ArrayList<>();
572
573StructuredChatCompletionCreateParams<List<Book>> params = ChatCompletionCreateParams.builder()
574 .responseFormat(books.getClass())
575 // ...
576 .build();
577```
578
579If an error occurs while converting a JSON response to an instance of a Java class, the error
580message will include the JSON response to assist in diagnosis. For instance, if the response is
581truncated, the JSON data will be incomplete and cannot be converted to a class instance. If your
582JSON response may contain sensitive information, avoid logging it directly, or ensure that you
583redact any sensitive details from the error message.
584
585### Local JSON schema validation
586
587Structured Outputs supports a
588[subset](https://platform.openai.com/docs/guides/structured-outputs#supported-schemas) of the JSON
589Schema language. Schemas are generated automatically from classes to align with this subset.
590However, due to the inherent structure of the classes, the generated schema may still violate
591certain OpenAI schema restrictions, such as exceeding the maximum nesting depth or utilizing
592unsupported data types.
593
594To facilitate compliance, the method `responseFormat(Class<T>)` performs a validation check on the
595schema derived from the specified class. This validation ensures that all restrictions are adhered
596to. If any issues are detected, an exception will be thrown, providing a detailed message outlining
597the reasons for the validation failure.
598
599- **Local Validation**: The validation process occurs locally, meaning no requests are sent to the
600 remote AI model. If the schema passes local validation, it is likely to pass remote validation as
601 well.
602- **Remote Validation**: The remote AI model will conduct its own validation upon receiving the JSON
603 schema in the request.
604- **Version Compatibility**: There may be instances where local validation fails while remote
605 validation succeeds. This can occur if the SDK version is outdated compared to the restrictions
606 enforced by the remote AI model.
607- **Disabling Local Validation**: If you encounter compatibility issues and wish to bypass local
608 validation, you can disable it by passing
609 [`JsonSchemaLocalValidation.NO`](openai-java-core/src/main/kotlin/com/openai/core/JsonSchemaLocalValidation.kt)
610 to the `responseFormat(Class<T>, JsonSchemaLocalValidation)` method when building the parameters.
611 (The default value for this parameter is `JsonSchemaLocalValidation.YES`.)
612
613```java
614import com.openai.core.JsonSchemaLocalValidation;
615import com.openai.models.ChatModel;
616import com.openai.models.chat.completions.ChatCompletionCreateParams;
617import com.openai.models.chat.completions.StructuredChatCompletionCreateParams;
618
619StructuredChatCompletionCreateParams<BookList> params = ChatCompletionCreateParams.builder()
620 .addUserMessage("List some famous late twentieth century novels.")
621 .model(ChatModel.GPT_5_2)
622 .responseFormat(BookList.class, JsonSchemaLocalValidation.NO)
623 .build();
624```
625
626By following these guidelines, you can ensure that your structured outputs conform to the necessary
627schema requirements and minimize the risk of remote validation errors.
628
629### Usage with the Responses API
630
631_Structured Outputs_ are also supported for the Responses API. The usage is the same as described
632except where the Responses API differs slightly from the Chat Completions API. Pass the top-level
633class to `text(Class<T>)` when building the parameters and then access an instance of the class from
634the generated message content in the response.
635
636You can start building the parameters with an instance of
637[`ResponseCreateParams.Builder`](openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseCreateParams.kt)
638or
639[`StructuredResponseCreateParams.Builder`](openai-java-core/src/main/kotlin/com/openai/models/responses/StructuredResponseCreateParams.kt).
640If you start with the former (which allows for more compact code) the builder type will change to
641the latter when `ResponseCreateParams.Builder.text(Class<T>)` is called.
642
643For a full example of the usage of _Structured Outputs_ with the Responses API, see
644[`ResponsesStructuredOutputsExample`](openai-java-example/src/main/java/com/openai/example/ResponsesStructuredOutputsExample.java).
645
646Instead of using `ResponseCreateParams.text(Class<T>)`, you can build a
647[`StructuredResponseTextConfig`](openai-java-core/src/main/kotlin/com/openai/models/responses/StructuredResponseTextConfig.kt)
648and set it on the `ResponseCreateParams` using the `text(StructuredResponseTextConfig)` method.
649Similar to using `ResponseCreateParams`, you can start with a `ResponseTextConfig.Builder` and its
650`format(Class<T>)` method will change it to a `StructuredResponseTextConfig.Builder`. This also
651allows you to set the `verbosity` configuration parameter on the text configuration before adding it
652to the `ResponseCreateParams`.
653
654For a full example of the usage of _Structured Outputs_ with the `ResponseTextConfig` and its
655`verbosity` parameter, see
656[`ResponsesStructuredOutputsVerbosityExample`](openai-java-example/src/main/java/com/openai/example/ResponsesStructuredOutputsVerbosityExample.java).
657
658### Usage with streaming
659
660_Structured Outputs_ can also be used with [Streaming](#streaming) and the Chat Completions API. As
661responses are returned in "chunks", the full response must first be accumulated to concatenate the
662JSON strings that can then be converted into instances of the arbitrary Java class. Normal streaming
663operations can be performed while accumulating the JSON strings.
664
665Use the [`ChatCompletionAccumulator`](openai-java-core/src/main/kotlin/com/openai/helpers/ChatCompletionAccumulator.kt)
666as described in the section on [Streaming helpers](#streaming-helpers) to accumulate the JSON
667strings. Once accumulated, use `ChatCompletionAccumulator.chatCompletion(Class<T>)` to convert the
668accumulated `ChatCompletion` into a
669[`StructuredChatCompletion`](openai-java-core/src/main/kotlin/com/openai/models/chat/completions/StructuredChatCompletion.kt).
670The `StructuredChatCompletion` can then automatically deserialize the JSON strings into instances of
671your Java class.
672
673For a full example of the usage of _Structured Outputs_ with Streaming and the Chat Completions API,
674see
675[`StructuredOutputsStreamingExample`](openai-java-example/src/main/java/com/openai/example/StructuredOutputsStreamingExample.java).
676
677With the Responses API, accumulate events while streaming using the
678[`ResponseAccumulator`](openai-java-core/src/main/kotlin/com/openai/helpers/ResponseAccumulator.kt).
679Once accumulated, use `ResponseAccumulator.response(Class<T>)` to convert the accumulated `Response`
680into a
681[`StructuredResponse`](openai-java-core/src/main/kotlin/com/openai/models/responses/StructuredResponse.kt).
682The [`StructuredResponse`] can then automatically deserialize the JSON strings into instances of
683your Java class.
684
685For a full example of the usage of _Structured Outputs_ with Streaming and the Responses API, see
686[`ResponsesStructuredOutputsStreamingExample`](openai-java-example/src/main/java/com/openai/example/ResponsesStructuredOutputsStreamingExample.java).
687
688### Defining JSON schema properties
689
690When a JSON schema is derived from your Java classes, all properties represented by `public` fields
691or `public` getter methods are included in the schema by default. Non-`public` fields and getter
692methods are _not_ included by default. You can exclude `public`, or include non-`public` fields or
693getter methods, by using the `@JsonIgnore` or `@JsonProperty` annotations respectively (see
694[Annotating classes and JSON schemas](#annotating-classes-and-json-schemas) for details).
695
696If you do not want to define `public` fields, you can define `private` fields and corresponding
697`public` getter methods. For example, a `private` field `myValue` with a `public` getter method
698`getMyValue()` will result in a `"myValue"` property being included in the JSON schema. If you
699prefer not to use the conventional Java "get" prefix for the name of the getter method, then you
700_must_ annotate the getter method with the `@JsonProperty` annotation and the full method name will
701be used as the property name. You do not have to define any corresponding setter methods if you do
702not need them.
703
704Each of your classes _must_ define at least one property to be included in the JSON schema. A
705validation error will occur if any class contains no fields or getter methods from which schema
706properties can be derived. This may occur if, for example:
707
708- There are no fields or getter methods in the class.
709- All fields and getter methods are `public`, but all are annotated with `@JsonIgnore`.
710- All fields and getter methods are non-`public`, but none are annotated with `@JsonProperty`.
711- A field or getter method is declared with a `Map` type. A `Map` is treated like a separate class
712 with no named properties, so it will result in an empty `"properties"` field in the JSON schema.
713 If you need arbitrary key/value data, model it as a list of entry objects with named fields so the
714 generated schema still has concrete property names.
715
716### Annotating classes and JSON schemas
717
718You can use annotations to add further information to the JSON schema derived from your Java
719classes, or to control which fields or getter methods will be included in the schema. Details from
720annotations captured in the JSON schema may be used by the AI model to improve its response. The SDK
721supports the use of [Jackson Databind](https://github.com/FasterXML/jackson-databind) annotations.
722
723```java
724import com.fasterxml.jackson.annotation.JsonClassDescription;
725import com.fasterxml.jackson.annotation.JsonIgnore;
726import com.fasterxml.jackson.annotation.JsonPropertyDescription;
727
728class Person {
729 @JsonPropertyDescription("The first name and surname of the person")
730 public String name;
731 public int birthYear;
732 @JsonPropertyDescription("The year the person died, or 'present' if the person is living.")
733 public String deathYear;
734}
735
736@JsonClassDescription("The details of one published book")
737class Book {
738 public String title;
739 public Person author;
740 @JsonPropertyDescription("The year in which the book was first published.")
741 public int publicationYear;
742 @JsonIgnore public String genre;
743}
744
745class BookList {
746 public List<Book> books;
747}
748```
749
750- Use `@JsonClassDescription` to add a detailed description to a class.
751- Use `@JsonPropertyDescription` to add a detailed description to a field or getter method of a
752 class.
753- Use `@JsonIgnore` to exclude a `public` field or getter method of a class from the generated JSON
754 schema.
755- Use `@JsonProperty` to include a non-`public` field or getter method of a class in the generated
756 JSON schema.
757
758If you use `@JsonProperty(required = false)`, the `false` value will be ignored. OpenAI JSON schemas
759must mark all properties as _required_, so the schema generated from your Java classes will respect
760that restriction and ignore any annotation that would violate it.
761
762You can also use [OpenAPI Swagger 2](https://swagger.io/specification/v2/)
763[`@Schema`](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations#schema) and
764[`@ArraySchema`](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations#arrayschema)
765annotations. These allow type-specific constraints to be added to your schema properties. You can
766learn more about the supported constraints in the OpenAI documentation on
767[Supported properties](https://platform.openai.com/docs/guides/structured-outputs#supported-properties).
768
769```java
770import io.swagger.v3.oas.annotations.media.Schema;
771import io.swagger.v3.oas.annotations.media.ArraySchema;
772
773class Article {
774 @ArraySchema(minItems = 1, maxItems = 10)
775 public List<String> authors;
776
777 @Schema(pattern = "^[A-Za-z ]+$")
778 public String title;
779
780 @Schema(format = "date")
781 public String publicationDate;
782
783 @Schema(minimum = "1")
784 public int pageCount;
785}
786```
787
788Local validation will check that you have not used any unsupported constraint keywords. However, the
789values of the constraints are _not_ validated locally. For example, if you use a value for the
790`"format"` constraint of a string property that is not in the list of
791[supported format names](https://platform.openai.com/docs/guides/structured-outputs#supported-properties),
792then local validation will pass, but the AI model may report an error.
793
794If you use both Jackson and Swagger annotations to set the same schema field, the Jackson annotation
795will take precedence. In the following example, the description of `myProperty` will be set to
796"Jackson description"; "Swagger description" will be ignored:
797
798```java
799import com.fasterxml.jackson.annotation.JsonPropertyDescription;
800import io.swagger.v3.oas.annotations.media.Schema;
801
802class MyObject {
803 @Schema(description = "Swagger description")
804 @JsonPropertyDescription("Jackson description")
805 public String myProperty;
806}
807```
808
809## Function calling with JSON schemas
810
811OpenAI [Function Calling](https://platform.openai.com/docs/guides/function-calling?api-mode=chat)
812lets you integrate external functions directly into the language model's responses. Instead of
813producing plain text, the model can output instructions (with parameters) for calling a function
814when appropriate. You define a [JSON schema](https://json-schema.org/overview/what-is-jsonschema)
815for functions, and the model uses it to decide when and how to trigger these calls, enabling more
816interactive, data-driven applications.
817
818A JSON schema describing a function's parameters can be defined via the API by building a
819[`ChatCompletionTool`](openai-java-core/src/main/kotlin/com/openai/models/chat/completions/ChatCompletionTool.kt)
820containing a
821[`FunctionDefinition`](openai-java-core/src/main/kotlin/com/openai/models/FunctionDefinition.kt)
822and then using `addTool` to set it on the input parameters. The response from the AI model may then
823contain requests to call your functions, detailing the functions' names and their parameter values
824as JSON data that conforms to the JSON schema from the function definition. You can then parse the
825parameter values from this JSON, invoke your functions, and pass your functions' results back to the
826AI model. A full, working example of _Function Calling_ using the low-level API can be seen in
827[`FunctionCallingRawExample`](openai-java-example/src/main/java/com/openai/example/FunctionCallingRawExample.java).
828
829However, for greater convenience, the SDK can derive a function and its parameters automatically
830from the structure of an arbitrary Java class: the class's name provides the function name, and the
831class's fields define the function's parameters. When the AI model responds with the parameter
832values in JSON form, you can then easily convert that JSON to an instance of your Java class and
833use the parameter values to invoke your custom function. A full, working example of the use of
834_Function Calling_ with Java classes to define function parameters can be seen in
835[`FunctionCallingExample`](openai-java-example/src/main/java/com/openai/example/FunctionCallingExample.java).
836
837Like for [Structured Outputs](#structured-outputs-with-json-schemas), Java classes can contain
838fields declared to be instances of other classes and can use collections (see
839[Defining JSON schema properties](#defining-json-schema-properties) for more details). Optionally,
840annotations can be used to set the descriptions of the function (class) and its parameters (fields)
841to assist the AI model in understanding the purpose of the function and the possible values of its
842parameters.
843
844```java
845import com.fasterxml.jackson.annotation.JsonClassDescription;
846import com.fasterxml.jackson.annotation.JsonPropertyDescription;
847
848@JsonClassDescription("Gets the quality of the given SDK.")
849static class GetSdkQuality {
850 @JsonPropertyDescription("The name of the SDK.")
851 public String name;
852
853 public SdkQuality execute() {
854 return new SdkQuality(
855 name, name.contains("OpenAI") ? "It's robust and polished!" : "*shrug*");
856 }
857}
858
859static class SdkQuality {
860 public String quality;
861
862 public SdkQuality(String name, String evaluation) {
863 quality = name + ": " + evaluation;
864 }
865}
866
867@JsonClassDescription("Gets the review score (out of 10) for the named SDK.")
868static class GetSdkScore {
869 public String name;
870
871 public int execute() {
872 return name.contains("OpenAI") ? 10 : 3;
873 }
874}
875```
876
877When your functions are defined, add them to the input parameters using `addTool(Class<T>)` and then
878call them if requested to do so in the AI model's response. `Function.arguments(Class<T>)` can be
879used to parse a function's parameters in JSON form to an instance of your function-defining class.
880The fields of that instance will be set to the values of the parameters to the function call.
881
882After calling the function, use `ChatCompletionToolMessageParam.Builder.contentAsJson(Object)` to
883pass the function's result back to the AI model. The method will convert the result to JSON form
884for consumption by the model. The `Object` can be any object, including simple `String` instances
885and boxed primitive types.
886
887```java
888import com.openai.client.OpenAIClient;
889import com.openai.client.okhttp.OpenAIOkHttpClient;
890import com.openai.models.ChatModel;
891import com.openai.models.chat.completions.*;
892import java.util.Collection;
893
894OpenAIClient client = OpenAIOkHttpClient.fromEnv();
895
896ChatCompletionCreateParams.Builder createParamsBuilder = ChatCompletionCreateParams.builder()
897 .model(ChatModel.GPT_3_5_TURBO)
898 .maxCompletionTokens(2048)
899 .addTool(GetSdkQuality.class)
900 .addTool(GetSdkScore.class)
901 .addUserMessage("How good are the following SDKs and what do reviewers say: "
902 + "OpenAI Java SDK, Unknown Company SDK.");
903
904client.chat().completions().create(createParamsBuilder.build()).choices().stream()
905 .map(ChatCompletion.Choice::message)
906 // Add each assistant message onto the builder so that we keep track of the
907 // conversation for asking a follow-up question later.
908 .peek(createParamsBuilder::addMessage)
909 .flatMap(message -> {
910 message.content().ifPresent(System.out::println);
911 return message.toolCalls().stream().flatMap(Collection::stream);
912 })
913 .forEach(toolCall -> {
914 Object result = callFunction(toolCall.function());
915 // Add the tool call result to the conversation.
916 createParamsBuilder.addMessage(ChatCompletionToolMessageParam.builder()
917 .toolCallId(toolCall.id())
918 .contentAsJson(result)
919 .build());
920 });
921
922// Ask a follow-up question about the function call result.
923createParamsBuilder.addUserMessage("Why do you say that?");
924client.chat().completions().create(createParamsBuilder.build()).choices().stream()
925 .flatMap(choice -> choice.message().content().stream())
926 .forEach(System.out::println);
927
928static Object callFunction(ChatCompletionMessageToolCall.Function function) {
929 switch (function.name()) {
930 case "GetSdkQuality":
931 return function.arguments(GetSdkQuality.class).execute();
932 case "GetSdkScore":
933 return function.arguments(GetSdkScore.class).execute();
934 default:
935 throw new IllegalArgumentException("Unknown function: " + function.name());
936 }
937}
938```
939
940In the code above, an `execute()` method encapsulates each function's logic. However, there is no
941requirement to follow that pattern. You are free to implement your function's logic in any way that
942best suits your use case. The pattern above is only intended to _suggest_ that a suitable pattern
943may make the process of function calling simpler to understand and implement.
944
945### Usage with the Responses API
946
947_Function Calling_ is also supported for the Responses API. The usage is the same as described
948except where the Responses API differs slightly from the Chat Completions API. Pass the top-level
949class to `addTool(Class<T>)` when building the parameters. In the response, look for
950[`RepoonseOutputItem`](openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseOutputItem.kt)
951instances that are function calls. Parse the parameters to each function call to an instance of the
952class using
953[`ResponseFunctionToolCall.arguments(Class<T>)`](openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseFunctionToolCall.kt).
954Finally, pass the result of each call back to the model.
955
956For a full example of the usage of _Function Calling_ with the Responses API using the low-level
957API to define and parse function parameters, see
958[`ResponsesFunctionCallingRawExample`](openai-java-example/src/main/java/com/openai/example/ResponsesFunctionCallingRawExample.java).
959
960For a full example of the usage of _Function Calling_ with the Responses API using Java classes to
961define and parse function parameters, see
962[`ResponsesFunctionCallingExample`](openai-java-example/src/main/java/com/openai/example/ResponsesFunctionCallingExample.java).
963
964### Local function JSON schema validation
965
966Like for _Structured Outputs_, you can perform local validation to check that the JSON schema
967derived from your function class respects the restrictions imposed by OpenAI on such schemas. Local
968validation is enabled by default, but it can be disabled by adding `JsonSchemaLocalValidation.NO` to
969the call to `addTool`.
970
971```java
972ChatCompletionCreateParams.Builder createParamsBuilder = ChatCompletionCreateParams.builder()
973 .model(ChatModel.GPT_3_5_TURBO)
974 .maxCompletionTokens(2048)
975 .addTool(GetSdkQuality.class, JsonSchemaLocalValidation.NO)
976 .addTool(GetSdkScore.class, JsonSchemaLocalValidation.NO)
977 .addUserMessage("How good are the following SDKs and what do reviewers say: "
978 + "OpenAI Java SDK, Unknown Company SDK.");
979```
980
981See [Local JSON schema validation](#local-json-schema-validation) for more details on local schema
982validation and under what circumstances you might want to disable it.
983
984### Annotating function classes
985
986You can use annotations to add further information about functions to the JSON schemas that are
987derived from your function classes, or to control which fields or getter methods will be used as
988parameters to the function. Details from annotations captured in the JSON schema may be used by the
989AI model to improve its response. The SDK supports the use of
990[Jackson Databind](https://github.com/FasterXML/jackson-databind) annotations.
991
992- Use `@JsonClassDescription` to add a description to a function class detailing when and how to use
993 that function.
994- Use `@JsonTypeName` to set the function name to something other than the simple name of the class,
995 which is used by default.
996- Use `@JsonPropertyDescription` to add a detailed description to function parameter (a field or
997 getter method of a function class).
998- Use `@JsonIgnore` to exclude a `public` field or getter method of a class from the generated JSON
999 schema for a function's parameters.
1000- Use `@JsonProperty` to include a non-`public` field or getter method of a class in the generated
1001 JSON schema for a function's parameters.
1002
1003OpenAI provides some
1004[Best practices for defining functions](https://platform.openai.com/docs/guides/function-calling#best-practices-for-defining-functions)
1005that may help you to understand how to use the above annotations effectively for your functions.
1006
1007See also [Defining JSON schema properties](#defining-json-schema-properties) for more details on how
1008to use fields and getter methods and combine access modifiers and annotations to define the
1009parameters of your functions. The same rules apply to function classes and to the structured output
1010classes described in that section.
1011
1012## File uploads
1013
1014The SDK defines methods that accept files.
1015
1016To upload a file, pass a [`Path`](https://docs.oracle.com/javase/8/docs/api/java/nio/file/Path.html):
1017
1018```java
1019import com.openai.models.files.FileCreateParams;
1020import com.openai.models.files.FileObject;
1021import com.openai.models.files.FilePurpose;
1022import java.nio.file.Paths;
1023
1024FileCreateParams params = FileCreateParams.builder()
1025 .purpose(FilePurpose.FINE_TUNE)
1026 .file(Paths.get("input.jsonl"))
1027 .build();
1028FileObject fileObject = client.files().create(params);
1029```
1030
1031Or an arbitrary [`InputStream`](https://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html):
1032
1033```java
1034import com.openai.models.files.FileCreateParams;
1035import com.openai.models.files.FileObject;
1036import com.openai.models.files.FilePurpose;
1037import java.net.URL;
1038
1039FileCreateParams params = FileCreateParams.builder()
1040 .purpose(FilePurpose.FINE_TUNE)
1041 .file(new URL("https://example.com/input.jsonl").openStream())
1042 .build();
1043FileObject fileObject = client.files().create(params);
1044```
1045
1046Or a `byte[]` array:
1047
1048```java
1049import com.openai.models.files.FileCreateParams;
1050import com.openai.models.files.FileObject;
1051import com.openai.models.files.FilePurpose;
1052
1053FileCreateParams params = FileCreateParams.builder()
1054 .purpose(FilePurpose.FINE_TUNE)
1055 .file("content".getBytes())
1056 .build();
1057FileObject fileObject = client.files().create(params);
1058```
1059
1060Note that when passing a non-`Path` its filename is unknown so it will not be included in the request. To manually set a filename, pass a [`MultipartField`](openai-java-core/src/main/kotlin/com/openai/core/Values.kt):
1061
1062```java
1063import com.openai.core.MultipartField;
1064import com.openai.models.files.FileCreateParams;
1065import com.openai.models.files.FileObject;
1066import com.openai.models.files.FilePurpose;
1067import java.io.InputStream;
1068import java.net.URL;
1069
1070FileCreateParams params = FileCreateParams.builder()
1071 .purpose(FilePurpose.FINE_TUNE)
1072 .file(MultipartField.<InputStream>builder()
1073 .value(new URL("https://example.com/input.jsonl").openStream())
1074 .filename("input.jsonl")
1075 .build())
1076 .build();
1077FileObject fileObject = client.files().create(params);
1078```
1079
1080## Webhook Verification
1081
1082Verifying webhook signatures is _optional but encouraged_.
1083
1084For more information about webhooks, see [the API docs](https://platform.openai.com/docs/guides/webhooks).
1085
1086### Parsing webhook payloads
1087
1088For most use cases, you will likely want to verify the webhook and parse the payload at the same time. To achieve this, we provide the method `client.webhooks().unwrap()`, which parses a webhook request and verifies that it was sent by OpenAI. This method will throw an exception if the signature is invalid.
1089
1090Note that the `body` parameter must be the raw JSON string sent from the server (do not parse it first). The `.unwrap()` method will parse this JSON for you into an event object after verifying the webhook was sent from OpenAI.
1091
1092```java
1093import com.openai.client.OpenAIClient;
1094import com.openai.client.okhttp.OpenAIOkHttpClient;
1095import com.openai.core.http.Headers;
1096import com.openai.models.webhooks.UnwrapWebhookEvent;
1097import java.util.Optional;
1098
1099OpenAIClient client = OpenAIOkHttpClient.fromEnv(); // OPENAI_WEBHOOK_SECRET env var used by default
1100
1101public void handleWebhook(String body, Map<String, String> headers) {
1102 try {
1103 Headers headersList = Headers.builder()
1104 .putAll(headers)
1105 .build();
1106
1107 UnwrapWebhookEvent event = client.webhooks().unwrap(body, headersList, Optional.empty());
1108
1109 if (event.isResponseCompletedWebhookEvent()) {
1110 System.out.println("Response completed: " + event.asResponseCompletedWebhookEvent().data());
1111 } else if (event.isResponseFailed()) {
1112 System.out.println("Response failed: " + event.asResponseFailed().data());
1113 } else {
1114 System.out.println("Unhandled event type: " + event.getClass().getSimpleName());
1115 }
1116 } catch (Exception e) {
1117 System.err.println("Invalid webhook signature: " + e.getMessage());
1118 // Handle invalid signature
1119 }
1120}
1121```
1122
1123### Verifying webhook payloads directly
1124
1125In some cases, you may want to verify the webhook separately from parsing the payload. If you prefer to handle these steps separately, we provide the method `client.webhooks().verifySignature()` to _only verify_ the signature of a webhook request. Like `.unwrap()`, this method will throw an exception if the signature is invalid.
1126
1127Note that the `body` parameter must be the raw JSON string sent from the server (do not parse it first). You will then need to parse the body after verifying the signature.
1128
1129```java
1130import com.fasterxml.jackson.databind.ObjectMapper;
1131import com.openai.client.OpenAIClient;
1132import com.openai.client.okhttp.OpenAIOkHttpClient;
1133import com.openai.core.http.Headers;
1134import com.openai.models.webhooks.WebhookVerificationParams;
1135import java.util.Optional;
1136
1137OpenAIClient client = OpenAIOkHttpClient.fromEnv(); // OPENAI_WEBHOOK_SECRET env var used by default
1138ObjectMapper objectMapper = new ObjectMapper();
1139
1140public void handleWebhook(String body, Map<String, String> headers) {
1141 try {
1142 Headers headersList = Headers.builder()
1143 .putAll(headers)
1144 .build();
1145
1146 client.webhooks().verifySignature(
1147 WebhookVerificationParams.builder()
1148 .payload(body)
1149 .headers(headersList)
1150 .build()
1151 );
1152
1153 // Parse the body after verification
1154 Map<String, Object> event = objectMapper.readValue(body, Map.class);
1155 System.out.println("Verified event: " + event);
1156 } catch (Exception e) {
1157 System.err.println("Invalid webhook signature: " + e.getMessage());
1158 // Handle invalid signature
1159 }
1160}
1161```
1162
1163## Binary responses
1164
1165The SDK defines methods that return binary responses, which are used for API responses that shouldn't necessarily be parsed, like non-JSON data.
1166
1167These methods return [`HttpResponse`](openai-java-core/src/main/kotlin/com/openai/core/http/HttpResponse.kt):
1168
1169```java
1170import com.openai.core.http.HttpResponse;
1171import com.openai.models.files.FileContentParams;
1172
1173HttpResponse response = client.files().content("file_id");
1174```
1175
1176To save the response content to a file, use the [`Files.copy(...)`](https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#copy-java.io.InputStream-java.nio.file.Path-java.nio.file.CopyOption...-) method:
1177
1178```java
1179import com.openai.core.http.HttpResponse;
1180import java.nio.file.Files;
1181import java.nio.file.Paths;
1182import java.nio.file.StandardCopyOption;
1183
1184try (HttpResponse response = client.files().content(params)) {
1185 Files.copy(
1186 response.body(),
1187 Paths.get(path),
1188 StandardCopyOption.REPLACE_EXISTING
1189 );
1190} catch (Exception e) {
1191 System.out.println("Something went wrong!");
1192 throw new RuntimeException(e);
1193}
1194```
1195
1196Or transfer the response content to any [`OutputStream`](https://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html):
1197
1198```java
1199import com.openai.core.http.HttpResponse;
1200import java.nio.file.Files;
1201import java.nio.file.Paths;
1202
1203try (HttpResponse response = client.files().content(params)) {
1204 response.body().transferTo(Files.newOutputStream(Paths.get(path)));
1205} catch (Exception e) {
1206 System.out.println("Something went wrong!");
1207 throw new RuntimeException(e);
1208}
1209```
1210
1211## Raw responses
1212
1213The SDK defines methods that deserialize responses into instances of Java classes. However, these methods don't provide access to the response headers, status code, or the raw response body.
1214
1215To access this data, prefix any HTTP method call on a client or service with `withRawResponse()`:
1216
1217```java
1218import com.openai.core.http.Headers;
1219import com.openai.core.http.HttpResponseFor;
1220import com.openai.models.ChatModel;
1221import com.openai.models.chat.completions.ChatCompletion;
1222import com.openai.models.chat.completions.ChatCompletionCreateParams;
1223
1224ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
1225 .addUserMessage("Say this is a test")
1226 .model(ChatModel.GPT_5_2)
1227 .build();
1228HttpResponseFor<ChatCompletion> chatCompletion = client.chat().completions().withRawResponse().create(params);
1229
1230int statusCode = chatCompletion.statusCode();
1231Headers headers = chatCompletion.headers();
1232```
1233
1234You can still deserialize the response into an instance of a Java class if needed:
1235
1236```java
1237import com.openai.models.chat.completions.ChatCompletion;
1238
1239ChatCompletion parsedChatCompletion = chatCompletion.parse();
1240```
1241
1242### Request IDs
1243
1244> For more information on debugging requests, see [the API docs](https://platform.openai.com/docs/api-reference/debugging-requests).
1245
1246When using raw responses, you can access the `x-request-id` response header using the `requestId()` method:
1247
1248```java
1249import com.openai.core.http.HttpResponseFor;
1250import com.openai.models.chat.completions.ChatCompletion;
1251import java.util.Optional;
1252
1253HttpResponseFor<ChatCompletion> chatCompletion = client.chat().completions().withRawResponse().create(params);
1254Optional<String> requestId = chatCompletion.requestId();
1255```
1256
1257This can be used to quickly log failing requests and report them back to OpenAI.
1258
1259## Error handling
1260
1261The SDK throws custom unchecked exception types:
1262
1263- [`OpenAIServiceException`](openai-java-core/src/main/kotlin/com/openai/errors/OpenAIServiceException.kt): Base class for HTTP errors. See this table for which exception subclass is thrown for each HTTP status code:
1264
1265 | Status | Exception |
1266 | ------ | ---------------------------------------------------------------------------------------------------------------------- |
1267 | 400 | [`BadRequestException`](openai-java-core/src/main/kotlin/com/openai/errors/BadRequestException.kt) |
1268 | 401 | [`UnauthorizedException`](openai-java-core/src/main/kotlin/com/openai/errors/UnauthorizedException.kt) |
1269 | 403 | [`PermissionDeniedException`](openai-java-core/src/main/kotlin/com/openai/errors/PermissionDeniedException.kt) |
1270 | 404 | [`NotFoundException`](openai-java-core/src/main/kotlin/com/openai/errors/NotFoundException.kt) |
1271 | 422 | [`UnprocessableEntityException`](openai-java-core/src/main/kotlin/com/openai/errors/UnprocessableEntityException.kt) |
1272 | 429 | [`RateLimitException`](openai-java-core/src/main/kotlin/com/openai/errors/RateLimitException.kt) |
1273 | 5xx | [`InternalServerException`](openai-java-core/src/main/kotlin/com/openai/errors/InternalServerException.kt) |
1274 | others | [`UnexpectedStatusCodeException`](openai-java-core/src/main/kotlin/com/openai/errors/UnexpectedStatusCodeException.kt) |
1275
1276 [`SseException`](openai-java-core/src/main/kotlin/com/openai/errors/SseException.kt) is thrown for errors encountered during [SSE streaming](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) after a successful initial HTTP response.
1277
1278- [`OpenAIIoException`](openai-java-core/src/main/kotlin/com/openai/errors/OpenAIIoException.kt): I/O networking errors.
1279
1280- [`OpenAIRetryableException`](openai-java-core/src/main/kotlin/com/openai/errors/OpenAIRetryableException.kt): Generic error indicating a failure that could be retried by the client.
1281
1282- [`OpenAIInvalidDataException`](openai-java-core/src/main/kotlin/com/openai/errors/OpenAIInvalidDataException.kt): Failure to interpret successfully parsed data. For example, when accessing a property that's supposed to be required, but the API unexpectedly omitted it from the response.
1283
1284- [`OpenAIException`](openai-java-core/src/main/kotlin/com/openai/errors/OpenAIException.kt): Base class for all exceptions. Most errors will result in one of the previously mentioned ones, but completely generic errors may be thrown using the base class.
1285
1286## Pagination
1287
1288The SDK defines methods that return a paginated lists of results. It provides convenient ways to access the results either one page at a time or item-by-item across all pages.
1289
1290### Auto-pagination
1291
1292To iterate through all results across all pages, use the `autoPager()` method, which automatically fetches more pages as needed.
1293
1294When using the synchronous client, the method returns an [`Iterable`](https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html)
1295
1296```java
1297import com.openai.models.finetuning.jobs.FineTuningJob;
1298import com.openai.models.finetuning.jobs.JobListPage;
1299
1300JobListPage page = client.fineTuning().jobs().list();
1301
1302// Process as an Iterable
1303for (FineTuningJob job : page.autoPager()) {
1304 System.out.println(job);
1305}
1306
1307// Process as a Stream
1308page.autoPager()
1309 .stream()
1310 .limit(50)
1311 .forEach(job -> System.out.println(job));
1312```
1313
1314When using the asynchronous client, the method returns an [`AsyncStreamResponse`](openai-java-core/src/main/kotlin/com/openai/core/http/AsyncStreamResponse.kt):
1315
1316```java
1317import com.openai.core.http.AsyncStreamResponse;
1318import com.openai.models.finetuning.jobs.FineTuningJob;
1319import com.openai.models.finetuning.jobs.JobListPageAsync;
1320import java.util.Optional;
1321import java.util.concurrent.CompletableFuture;
1322
1323CompletableFuture<JobListPageAsync> pageFuture = client.async().fineTuning().jobs().list();
1324
1325pageFuture.thenRun(page -> page.autoPager().subscribe(job -> {
1326 System.out.println(job);
1327}));
1328
1329// If you need to handle errors or completion of the stream
1330pageFuture.thenRun(page -> page.autoPager().subscribe(new AsyncStreamResponse.Handler<>() {
1331 @Override
1332 public void onNext(FineTuningJob job) {
1333 System.out.println(job);
1334 }
1335
1336 @Override
1337 public void onComplete(Optional<Throwable> error) {
1338 if (error.isPresent()) {
1339 System.out.println("Something went wrong!");
1340 throw new RuntimeException(error.get());
1341 } else {
1342 System.out.println("No more!");
1343 }
1344 }
1345}));
1346
1347// Or use futures
1348pageFuture.thenRun(page -> page.autoPager()
1349 .subscribe(job -> {
1350 System.out.println(job);
1351 })
1352 .onCompleteFuture()
1353 .whenComplete((unused, error) -> {
1354 if (error != null) {
1355 System.out.println("Something went wrong!");
1356 throw new RuntimeException(error);
1357 } else {
1358 System.out.println("No more!");
1359 }
1360 }));
1361```
1362
1363### Manual pagination
1364
1365To access individual page items and manually request the next page, use the `items()`,
1366`hasNextPage()`, and `nextPage()` methods:
1367
1368```java
1369import com.openai.models.finetuning.jobs.FineTuningJob;
1370import com.openai.models.finetuning.jobs.JobListPage;
1371
1372JobListPage page = client.fineTuning().jobs().list();
1373while (true) {
1374 for (FineTuningJob job : page.items()) {
1375 System.out.println(job);
1376 }
1377
1378 if (!page.hasNextPage()) {
1379 break;
1380 }
1381
1382 page = page.nextPage();
1383}
1384```
1385
1386## Logging
1387
1388Enable logging by setting the `OPENAI_LOG` environment variable to `info`:
1389
1390```sh
1391export OPENAI_LOG=info
1392```
1393
1394Or to `debug` for more verbose logging:
1395
1396```sh
1397export OPENAI_LOG=debug
1398```
1399
1400Or configure the client manually using the `logLevel` method:
1401
1402```java
1403import com.openai.client.OpenAIClient;
1404import com.openai.client.okhttp.OpenAIOkHttpClient;
1405import com.openai.core.LogLevel;
1406
1407OpenAIClient client = OpenAIOkHttpClient.builder()
1408 .fromEnv()
1409 .logLevel(LogLevel.INFO)
1410 .build();
1411```
1412
1413## ProGuard and R8
1414
1415Although the SDK uses reflection, it is still usable with [ProGuard](https://github.com/Guardsquare/proguard) and [R8](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization) because `openai-java-core` is published with a [configuration file](openai-java-core/src/main/resources/META-INF/proguard/openai-java-core.pro) containing [keep rules](https://www.guardsquare.com/manual/configuration/usage).
1416
1417ProGuard and R8 should automatically detect and use the published rules, but you can also manually copy the keep rules if necessary.
1418
1419## GraalVM
1420
1421Although the SDK uses reflection, it is still usable in [GraalVM](https://www.graalvm.org) because `openai-java-core` is published with [reachability metadata](https://www.graalvm.org/latest/reference-manual/native-image/metadata/).
1422
1423GraalVM should automatically detect and use the published metadata, but [manual configuration](https://www.graalvm.org/jdk24/reference-manual/native-image/overview/BuildConfiguration/) is also available.
1424
1425## Spring Boot
1426
1427If you're using Spring Boot, then you can use the SDK's [Spring Boot starter](https://docs.spring.io/spring-boot/docs/2.7.18/reference/htmlsingle/#using.build-systems.starters) to simplify configuration and get set up quickly.
1428
1429### Installation
1430
1431<!-- x-release-please-start-version -->
1432
1433#### Gradle
1434
1435```kotlin
1436implementation("com.openai:openai-java-spring-boot-starter:4.36.0")
1437```
1438
1439#### Maven
1440
1441```xml
1442<dependency>
1443 <groupId>com.openai</groupId>
1444 <artifactId>openai-java-spring-boot-starter</artifactId>
1445 <version>4.36.0</version>
1446</dependency>
1447```
1448
1449<!-- x-release-please-end -->
1450
1451### Configuration
1452
1453The [client's environment variable options](#client-configuration) can be configured in [`application.properties` or `application.yml`](https://docs.spring.io/spring-boot/how-to/properties-and-configuration.html).
1454
1455#### `application.properties`
1456
1457```properties
1458openai.base-url=https://api.openai.com/v1
1459openai.api-key=My API Key
1460openai.admin-key=My Admin API Key
1461openai.org-id=My Organization
1462openai.project-id=My Project
1463openai.webhook-secret=My Webhook Secret
1464```
1465
1466#### `application.yml`
1467
1468```yaml
1469openai:
1470 base-url: https://api.openai.com/v1
1471 api-key: My API Key
1472 admin-key: My Admin API Key
1473 org-id: My Organization
1474 project-id: My Project
1475 webhook-secret: My Webhook Secret
1476```
1477
1478#### Other configuration
1479
1480Configure any other client option by providing one or more instances of [`OpenAIClientCustomizer`](openai-java-spring-boot-starter/src/main/kotlin/com/openai/springboot/OpenAIClientCustomizer.kt). For example, here's how you'd set [`maxRetries`](#retries):
1481
1482```java
1483import com.openai.springboot.OpenAIClientCustomizer;
1484import org.springframework.context.annotation.Bean;
1485import org.springframework.context.annotation.Configuration;
1486
1487@Configuration
1488public class OpenAIConfig {
1489 @Bean
1490 public OpenAIClientCustomizer customizer() {
1491 return builder -> builder.maxRetries(3);
1492 }
1493}
1494```
1495
1496### Usage
1497
1498[Inject](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html) [`OpenAIClient`](openai-java-core/src/main/kotlin/com/openai/client/OpenAIClient.kt) anywhere and start using it!
1499
1500## Jackson
1501
1502The SDK depends on [Jackson](https://github.com/FasterXML/jackson) for JSON serialization/deserialization. It is compatible with version 2.13.4 or higher, but depends on version 2.18.2 by default.
1503
1504The SDK throws an exception if it detects an incompatible Jackson version at runtime (e.g. if the default version was overridden in your Maven or Gradle config).
1505
1506If the SDK threw an exception, but you're _certain_ the version is compatible, then disable the version check using the `checkJacksonVersionCompatibility` on [`OpenAIOkHttpClient`](openai-java-client-okhttp/src/main/kotlin/com/openai/client/okhttp/OpenAIOkHttpClient.kt) or [`OpenAIOkHttpClientAsync`](openai-java-client-okhttp/src/main/kotlin/com/openai/client/okhttp/OpenAIOkHttpClientAsync.kt).
1507
1508> [!CAUTION]
1509> We make no guarantee that the SDK works correctly when the Jackson version check is disabled.
1510
1511Also note that there are bugs in older Jackson versions that can affect the SDK. We don't work around all Jackson bugs ([example](https://github.com/FasterXML/jackson-databind/issues/3240)) and expect users to upgrade Jackson for those instead.
1512
1513## Microsoft Azure
1514
1515To use this library with [Azure OpenAI](https://learn.microsoft.com/azure/ai-services/openai/overview), use the same
1516OpenAI client builder but with the Azure-specific configuration.
1517
1518```java
1519OpenAIClient client = OpenAIOkHttpClient.builder()
1520 // Gets the API key and endpoint from the `AZURE_OPENAI_KEY` and `OPENAI_BASE_URL` environment variables, respectively
1521 .fromEnv()
1522 // Set the Azure Entra ID
1523 .credential(BearerTokenCredential.create(AuthenticationUtil.getBearerTokenSupplier(
1524 new DefaultAzureCredentialBuilder().build(), "https://cognitiveservices.azure.com/.default")))
1525 .build();
1526```
1527
1528See the complete Azure OpenAI example in the [`openai-java-example`](openai-java-example/src/main/java/com/openai/example/AzureEntraIdExample.java) directory. The other examples in the directory also work with Azure as long as the client is configured to use it.
1529
1530### Optional: URL path mode configuration
1531
1532The [`ClientOptions`](openai-java-core/src/main/kotlin/com/openai/core/ClientOptions.kt) can be configured to treat Azure OpenAI endpoint URLs differently, depending on your service setup. The default value is [`AzureUrlPathMode.AUTO`](openai-java-core/src/main/kotlin/com/openai/azure/AzureUrlPathMode.kt). To customize the SDK behavior, each value does the following:
1533- `AzureUrlPathMode.LEGACY`: forces the deployment or model name into the path.
1534- `AzureUrlPathMode.UNIFIED`: for newer endpoints ending in `/openai/v1` the service behaviour matches OpenAI's, therefore [`AzureOpenAIServiceVersion`](openai-java-core/src/main/kotlin/com/openai/azure/AzureOpenAIServiceVersion.kt) becomes optional and the model is passed in the request object.
1535- `AzureUrlPathMode.AUTO`: automatically detects the path mode based on the base URL. Default value.
1536
1537## Network options
1538
1539### Retries
1540
1541The SDK automatically retries 2 times by default, with a short exponential backoff between requests.
1542
1543Only the following error types are retried:
1544
1545- Connection errors (for example, due to a network connectivity problem)
1546- 408 Request Timeout
1547- 409 Conflict
1548- 429 Rate Limit
1549- 5xx Internal
1550
1551The API may also explicitly instruct the SDK to retry or not retry a request.
1552
1553To set a custom number of retries, configure the client using the `maxRetries` method:
1554
1555```java
1556import com.openai.client.OpenAIClient;
1557import com.openai.client.okhttp.OpenAIOkHttpClient;
1558
1559OpenAIClient client = OpenAIOkHttpClient.builder()
1560 .fromEnv()
1561 .maxRetries(4)
1562 .build();
1563```
1564
1565### Timeouts
1566
1567Requests time out after 10 minutes by default.
1568
1569To set a custom timeout, configure the method call using the `timeout` method:
1570
1571```java
1572import com.openai.models.chat.completions.ChatCompletion;
1573
1574ChatCompletion chatCompletion = client.chat().completions().create(
1575 params, RequestOptions.builder().timeout(Duration.ofSeconds(30)).build()
1576);
1577```
1578
1579Or configure the default for all method calls at the client level:
1580
1581```java
1582import com.openai.client.OpenAIClient;
1583import com.openai.client.okhttp.OpenAIOkHttpClient;
1584import java.time.Duration;
1585
1586OpenAIClient client = OpenAIOkHttpClient.builder()
1587 .fromEnv()
1588 .timeout(Duration.ofSeconds(30))
1589 .build();
1590```
1591
1592### Proxies
1593
1594To route requests through a proxy, configure the client using the `proxy` method:
1595
1596```java
1597import com.openai.client.OpenAIClient;
1598import com.openai.client.okhttp.OpenAIOkHttpClient;
1599import java.net.InetSocketAddress;
1600import java.net.Proxy;
1601
1602OpenAIClient client = OpenAIOkHttpClient.builder()
1603 .fromEnv()
1604 .proxy(new Proxy(
1605 Proxy.Type.HTTP, new InetSocketAddress(
1606 "https://example.com", 8080
1607 )
1608 ))
1609 .build();
1610```
1611
1612If the proxy responds with `407 Proxy Authentication Required`, supply credentials by also configuring `proxyAuthenticator`:
1613
1614```java
1615import com.openai.client.OpenAIClient;
1616import com.openai.client.okhttp.OpenAIOkHttpClient;
1617import com.openai.core.http.ProxyAuthenticator;
1618
1619OpenAIClient client = OpenAIOkHttpClient.builder()
1620 .fromEnv()
1621 .proxy(...)
1622 // Or a custom implementation of `ProxyAuthenticator`.
1623 .proxyAuthenticator(ProxyAuthenticator.basic("username", "password"))
1624 .build();
1625```
1626
1627### Connection pooling
1628
1629To customize the underlying OkHttp connection pool, configure the client using the `maxIdleConnections` and `keepAliveDuration` methods:
1630
1631```java
1632import com.openai.client.OpenAIClient;
1633import com.openai.client.okhttp.OpenAIOkHttpClient;
1634import java.time.Duration;
1635
1636OpenAIClient client = OpenAIOkHttpClient.builder()
1637 .fromEnv()
1638 // If `maxIdleConnections` is set, then `keepAliveDuration` must be set, and vice versa.
1639 .maxIdleConnections(10)
1640 .keepAliveDuration(Duration.ofMinutes(2))
1641 .build();
1642```
1643
1644If both options are unset, OkHttp's default connection pool settings are used.
1645
1646### HTTPS
1647
1648> [!NOTE]
1649> Most applications should not call these methods, and instead use the system defaults. The defaults include
1650> special optimizations that can be lost if the implementations are modified.
1651
1652To configure how HTTPS connections are secured, configure the client using the `sslSocketFactory`, `trustManager`, and `hostnameVerifier` methods:
1653
1654```java
1655import com.openai.client.OpenAIClient;
1656import com.openai.client.okhttp.OpenAIOkHttpClient;
1657
1658OpenAIClient client = OpenAIOkHttpClient.builder()
1659 .fromEnv()
1660 // If `sslSocketFactory` is set, then `trustManager` must be set, and vice versa.
1661 .sslSocketFactory(yourSSLSocketFactory)
1662 .trustManager(yourTrustManager)
1663 .hostnameVerifier(yourHostnameVerifier)
1664 .build();
1665```
1666
1667### Custom HTTP client
1668
1669The SDK consists of three artifacts:
1670
1671- `openai-java-core`
1672 - Contains core SDK logic
1673 - Does not depend on [OkHttp](https://square.github.io/okhttp)
1674 - Exposes [`OpenAIClient`](openai-java-core/src/main/kotlin/com/openai/client/OpenAIClient.kt), [`OpenAIClientAsync`](openai-java-core/src/main/kotlin/com/openai/client/OpenAIClientAsync.kt), [`OpenAIClientImpl`](openai-java-core/src/main/kotlin/com/openai/client/OpenAIClientImpl.kt), and [`OpenAIClientAsyncImpl`](openai-java-core/src/main/kotlin/com/openai/client/OpenAIClientAsyncImpl.kt), all of which can work with any HTTP client
1675- `openai-java-client-okhttp`
1676 - Depends on [OkHttp](https://square.github.io/okhttp)
1677 - Exposes [`OpenAIOkHttpClient`](openai-java-client-okhttp/src/main/kotlin/com/openai/client/okhttp/OpenAIOkHttpClient.kt) and [`OpenAIOkHttpClientAsync`](openai-java-client-okhttp/src/main/kotlin/com/openai/client/okhttp/OpenAIOkHttpClientAsync.kt), which provide a way to construct [`OpenAIClientImpl`](openai-java-core/src/main/kotlin/com/openai/client/OpenAIClientImpl.kt) and [`OpenAIClientAsyncImpl`](openai-java-core/src/main/kotlin/com/openai/client/OpenAIClientAsyncImpl.kt), respectively, using OkHttp
1678- `openai-java`
1679 - Depends on and exposes the APIs of both `openai-java-core` and `openai-java-client-okhttp`
1680 - Does not have its own logic
1681
1682This structure allows replacing the SDK's default HTTP client without pulling in unnecessary dependencies.
1683
1684#### Customized [`OkHttpClient`](https://square.github.io/okhttp/3.x/okhttp/okhttp3/OkHttpClient.html)
1685
1686> [!TIP]
1687> Try the available [network options](#network-options) before replacing the default client.
1688
1689To use a customized `OkHttpClient`:
1690
16911. Replace your [`openai-java` dependency](#installation) with `openai-java-core`
16922. Copy `openai-java-client-okhttp`'s [`OkHttpClient`](openai-java-client-okhttp/src/main/kotlin/com/openai/client/okhttp/OkHttpClient.kt) class into your code and customize it
16933. Construct [`OpenAIClientImpl`](openai-java-core/src/main/kotlin/com/openai/client/OpenAIClientImpl.kt) or [`OpenAIClientAsyncImpl`](openai-java-core/src/main/kotlin/com/openai/client/OpenAIClientAsyncImpl.kt), similarly to [`OpenAIOkHttpClient`](openai-java-client-okhttp/src/main/kotlin/com/openai/client/okhttp/OpenAIOkHttpClient.kt) or [`OpenAIOkHttpClientAsync`](openai-java-client-okhttp/src/main/kotlin/com/openai/client/okhttp/OpenAIOkHttpClientAsync.kt), using your customized client
1694
1695### Completely custom HTTP client
1696
1697To use a completely custom HTTP client:
1698
16991. Replace your [`openai-java` dependency](#installation) with `openai-java-core`
17002. Write a class that implements the [`HttpClient`](openai-java-core/src/main/kotlin/com/openai/core/http/HttpClient.kt) interface
17013. Construct [`OpenAIClientImpl`](openai-java-core/src/main/kotlin/com/openai/client/OpenAIClientImpl.kt) or [`OpenAIClientAsyncImpl`](openai-java-core/src/main/kotlin/com/openai/client/OpenAIClientAsyncImpl.kt), similarly to [`OpenAIOkHttpClient`](openai-java-client-okhttp/src/main/kotlin/com/openai/client/okhttp/OpenAIOkHttpClient.kt) or [`OpenAIOkHttpClientAsync`](openai-java-client-okhttp/src/main/kotlin/com/openai/client/okhttp/OpenAIOkHttpClientAsync.kt), using your new client class
1702
1703## Undocumented API functionality
1704
1705The SDK is typed for convenient usage of the documented API. However, it also supports working with undocumented or not yet supported parts of the API.
1706
1707### Parameters
1708
1709To set undocumented parameters, call the `putAdditionalHeader`, `putAdditionalQueryParam`, or `putAdditionalBodyProperty` methods on any `Params` class:
1710
1711```java
1712import com.openai.core.JsonValue;
1713import com.openai.models.chat.completions.ChatCompletionCreateParams;
1714
1715ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
1716 .putAdditionalHeader("Secret-Header", "42")
1717 .putAdditionalQueryParam("secret_query_param", "42")
1718 .putAdditionalBodyProperty("secretProperty", JsonValue.from("42"))
1719 .build();
1720```
1721
1722These can be accessed on the built object later using the `_additionalHeaders()`, `_additionalQueryParams()`, and `_additionalBodyProperties()` methods.
1723
1724To set undocumented parameters on _nested_ headers, query params, or body classes, call the `putAdditionalProperty` method on the nested class:
1725
1726```java
1727import com.openai.core.JsonValue;
1728import com.openai.models.chat.completions.ChatCompletionCreateParams;
1729
1730ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
1731 .responseFormat(ChatCompletionCreateParams.ResponseFormat.builder()
1732 .putAdditionalProperty("secretProperty", JsonValue.from("42"))
1733 .build())
1734 .build();
1735```
1736
1737These properties can be accessed on the nested built object later using the `_additionalProperties()` method.
1738
1739To set a documented parameter or property to an undocumented or not yet supported _value_, pass a [`JsonValue`](openai-java-core/src/main/kotlin/com/openai/core/Values.kt) object to its setter:
1740
1741```java
1742import com.openai.core.JsonValue;
1743import com.openai.models.ChatModel;
1744import com.openai.models.chat.completions.ChatCompletionCreateParams;
1745
1746ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
1747 .messages(JsonValue.from(42))
1748 .model(ChatModel.GPT_5_2)
1749 .build();
1750```
1751
1752The most straightforward way to create a [`JsonValue`](openai-java-core/src/main/kotlin/com/openai/core/Values.kt) is using its `from(...)` method:
1753
1754```java
1755import com.openai.core.JsonValue;
1756import java.util.List;
1757import java.util.Map;
1758
1759// Create primitive JSON values
1760JsonValue nullValue = JsonValue.from(null);
1761JsonValue booleanValue = JsonValue.from(true);
1762JsonValue numberValue = JsonValue.from(42);
1763JsonValue stringValue = JsonValue.from("Hello World!");
1764
1765// Create a JSON array value equivalent to `["Hello", "World"]`
1766JsonValue arrayValue = JsonValue.from(List.of(
1767 "Hello", "World"
1768));
1769
1770// Create a JSON object value equivalent to `{ "a": 1, "b": 2 }`
1771JsonValue objectValue = JsonValue.from(Map.of(
1772 "a", 1,
1773 "b", 2
1774));
1775
1776// Create an arbitrarily nested JSON equivalent to:
1777// {
1778// "a": [1, 2],
1779// "b": [3, 4]
1780// }
1781JsonValue complexValue = JsonValue.from(Map.of(
1782 "a", List.of(
1783 1, 2
1784 ),
1785 "b", List.of(
1786 3, 4
1787 )
1788));
1789```
1790
1791Normally a `Builder` class's `build` method will throw [`IllegalStateException`](https://docs.oracle.com/javase/8/docs/api/java/lang/IllegalStateException.html) if any required parameter or property is unset.
1792
1793To forcibly omit a required parameter or property, pass [`JsonMissing`](openai-java-core/src/main/kotlin/com/openai/core/Values.kt):
1794
1795```java
1796import com.openai.core.JsonMissing;
1797import com.openai.models.ChatModel;
1798import com.openai.models.chat.completions.ChatCompletionCreateParams;
1799
1800ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
1801 .model(ChatModel.GPT_5_4)
1802 .messages(JsonMissing.of())
1803 .build();
1804```
1805
1806### Response properties
1807
1808To access undocumented response properties, call the `_additionalProperties()` method:
1809
1810```java
1811import com.openai.core.JsonValue;
1812import java.util.Map;
1813
1814Map<String, JsonValue> additionalProperties = client.chat().completions().create(params)._additionalProperties();
1815JsonValue secretPropertyValue = additionalProperties.get("secretProperty");
1816
1817String result = secretPropertyValue.accept(new JsonValue.Visitor<>() {
1818 @Override
1819 public String visitNull() {
1820 return "It's null!";
1821 }
1822
1823 @Override
1824 public String visitBoolean(boolean value) {
1825 return "It's a boolean!";
1826 }
1827
1828 @Override
1829 public String visitNumber(Number value) {
1830 return "It's a number!";
1831 }
1832
1833 // Other methods include `visitMissing`, `visitString`, `visitArray`, and `visitObject`
1834 // The default implementation of each unimplemented method delegates to `visitDefault`, which throws by default, but can also be overridden
1835});
1836```
1837
1838To access a property's raw JSON value, which may be undocumented, call its `_` prefixed method:
1839
1840```java
1841import com.openai.core.JsonField;
1842import com.openai.models.chat.completions.ChatCompletionMessageParam;
1843import java.util.Optional;
1844
1845JsonField<List<ChatCompletionMessageParam>> messages = client.chat().completions().create(params)._messages();
1846
1847if (messages.isMissing()) {
1848 // The property is absent from the JSON response
1849} else if (messages.isNull()) {
1850 // The property was set to literal null
1851} else {
1852 // Check if value was provided as a string
1853 // Other methods include `asNumber()`, `asBoolean()`, etc.
1854 Optional<String> jsonString = messages.asString();
1855
1856 // Try to deserialize into a custom type
1857 MyClass myObject = messages.asUnknown().orElseThrow().convert(MyClass.class);
1858}
1859```
1860
1861### Response validation
1862
1863In rare cases, the API may return a response that doesn't match the expected type. For example, the SDK may expect a property to contain a `String`, but the API could return something else.
1864
1865By default, the SDK will not throw an exception in this case. It will throw [`OpenAIInvalidDataException`](openai-java-core/src/main/kotlin/com/openai/errors/OpenAIInvalidDataException.kt) only if you directly access the property.
1866
1867Validating the response is _not_ forwards compatible with new types from the API for existing fields.
1868
1869If you would still prefer to check that the response is completely well-typed upfront, then either call `validate()`:
1870
1871```java
1872import com.openai.models.chat.completions.ChatCompletion;
1873
1874ChatCompletion chatCompletion = client.chat().completions().create(params).validate();
1875```
1876
1877Or configure the method call to validate the response using the `responseValidation` method:
1878
1879```java
1880import com.openai.models.chat.completions.ChatCompletion;
1881
1882ChatCompletion chatCompletion = client.chat().completions().create(
1883 params, RequestOptions.builder().responseValidation(true).build()
1884);
1885```
1886
1887Or configure the default for all method calls at the client level:
1888
1889```java
1890import com.openai.client.OpenAIClient;
1891import com.openai.client.okhttp.OpenAIOkHttpClient;
1892
1893OpenAIClient client = OpenAIOkHttpClient.builder()
1894 .fromEnv()
1895 .responseValidation(true)
1896 .build();
1897```
1898
1899## FAQ
1900
1901### Why don't you use plain `enum` classes?
1902
1903Java `enum` classes are not trivially [forwards compatible](https://www.stainless.com/blog/making-java-enums-forwards-compatible). Using them in the SDK could cause runtime exceptions if the API is updated to respond with a new enum value.
1904
1905### Why do you represent fields using `JsonField<T>` instead of just plain `T`?
1906
1907Using `JsonField<T>` enables a few features:
1908
1909- Allowing usage of [undocumented API functionality](#undocumented-api-functionality)
1910- Lazily [validating the API response against the expected shape](#response-validation)
1911- Representing absent vs explicitly null values
1912
1913### Why don't you use [`data` classes](https://kotlinlang.org/docs/data-classes.html)?
1914
1915It is not [backwards compatible to add new fields to a data class](https://kotlinlang.org/docs/api-guidelines-backward-compatibility.html#avoid-using-data-classes-in-your-api) and we don't want to introduce a breaking change every time we add a field to a class.
1916
1917### Why don't you use checked exceptions?
1918
1919Checked exceptions are widely considered a mistake in the Java programming language. In fact, they were omitted from Kotlin for this reason.
1920
1921Checked exceptions:
1922
1923- Are verbose to handle
1924- Encourage error handling at the wrong level of abstraction, where nothing can be done about the error
1925- Are tedious to propagate due to the [function coloring problem](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function)
1926- Don't play well with lambdas (also due to the function coloring problem)
1927
1928## Semantic versioning
1929
1930This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions:
1931
19321. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_
19332. Changes that we do not expect to impact the vast majority of users in practice.
1934
1935We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience.
1936
1937We are keen for your feedback; please open an [issue](https://www.github.com/openai/openai-java/issues) with questions, bugs, or suggestions.