openai/openai-java

Public

mirrored from https://github.com/openai/openai-javaAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v4.24.1

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

README.md

1827lines · modecode

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