OpenAI models support for Spring AI, providing comprehensive integration for chat completion, embeddings, image generation, audio transcription, text-to-speech, and content moderation capabilities within Spring Boot applications.
Low-level REST API clients for direct interaction with OpenAI's APIs, providing fine-grained control over requests and responses for chat, embeddings, images, audio, moderation, and file operations.
The spring-ai-openai module provides low-level API client classes for direct communication with OpenAI services. These clients offer maximum flexibility and control, handling HTTP communication, request/response serialization, and error handling.
When to use API clients:
For most use cases, prefer the high-level model classes (OpenAiChatModel, OpenAiEmbeddingModel, etc.) which provide Spring-idiomatic APIs and better integration with Spring AI features.
REST API client for chat completions and embeddings.
/**
* Low-level OpenAI API client for chat and embeddings
*/
public class OpenAiApi {
/**
* Default chat model constant (value: GPT_4_O)
*/
public static final ChatModel DEFAULT_CHAT_MODEL = ChatModel.GPT_4_O;
/**
* Default embedding model constant (value: "text-embedding-ada-002")
*/
public static final String DEFAULT_EMBEDDING_MODEL = "text-embedding-ada-002";
/**
* Create a builder for configuring OpenAiApi
* @return Builder instance
*/
public static Builder builder();
/**
* Create a builder from this API instance
* @return Builder pre-populated with current configuration
*/
public Builder mutate();
/**
* Synchronous chat completion request
* @param chatRequest Chat completion request
* @return ResponseEntity with ChatCompletion
*/
public ResponseEntity<ChatCompletion> chatCompletionEntity(ChatCompletionRequest chatRequest);
/**
* Synchronous chat completion with custom headers
* @param chatRequest Chat completion request
* @param headers HTTP headers to include
* @return ResponseEntity with ChatCompletion
*/
public ResponseEntity<ChatCompletion> chatCompletionEntity(
ChatCompletionRequest chatRequest,
MultiValueMap<String, String> headers
);
/**
* Streaming chat completion request
* @param chatRequest Chat completion request
* @return Flux of ChatCompletionChunk
*/
public Flux<ChatCompletionChunk> chatCompletionStream(ChatCompletionRequest chatRequest);
/**
* Streaming chat completion with custom headers
* @param chatRequest Chat completion request
* @param headers HTTP headers to include
* @return Flux of ChatCompletionChunk
*/
public Flux<ChatCompletionChunk> chatCompletionStream(
ChatCompletionRequest chatRequest,
MultiValueMap<String, String> headers
);
/**
* Generate embeddings for text input(s)
* @param embeddingRequest Embedding request
* @return ResponseEntity with EmbeddingList
*/
public <T> ResponseEntity<EmbeddingList<Embedding>> embeddings(EmbeddingRequest<T> embeddingRequest);
/**
* Extract text content from media content list
* @param content List of media content items
* @return Extracted text content
*/
public static String getTextContent(List<ChatCompletionMessage.MediaContent> content);
}Builder Pattern:
public static class Builder {
/**
* Set the OpenAI API key
* @param apiKey API key
* @return Builder
*/
public Builder apiKey(String apiKey);
/**
* Set the base URL for API requests
* @param baseUrl Base URL (default: https://api.openai.com)
* @return Builder
*/
public Builder baseUrl(String baseUrl);
/**
* Set the organization ID
* @param organizationId Organization ID
* @return Builder
*/
public Builder organizationId(String organizationId);
/**
* Set the project ID
* @param projectId Project ID
* @return Builder
*/
public Builder projectId(String projectId);
/**
* Set custom HTTP headers
* @param headers Headers to include in all requests
* @return Builder
*/
public Builder headers(Map<String, String> headers);
/**
* Set custom RestClient for HTTP communication
* @param restClient Configured RestClient
* @return Builder
*/
public Builder restClient(RestClient restClient);
/**
* Build the OpenAiApi instance
* @return Configured OpenAiApi
*/
public OpenAiApi build();
}Usage Example:
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.ai.openai.api.OpenAiApi.ChatCompletionRequest;
import org.springframework.ai.openai.api.OpenAiApi.ChatCompletionMessage;
import org.springframework.ai.openai.api.OpenAiApi.ChatCompletion;
import org.springframework.http.ResponseEntity;
// Create API client
var api = OpenAiApi.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.organizationId("org-123")
.baseUrl("https://api.openai.com")
.build();
// Synchronous chat completion
var request = new ChatCompletionRequest(
List.of(
new ChatCompletionMessage(
"What is the capital of France?",
ChatCompletionMessage.Role.USER,
null, null, null, null, null, null
)
),
OpenAiApi.ChatModel.GPT_4_O.getValue(),
null, // frequencyPenalty
null, // logitBias
null, // logprobs
null, // topLogprobs
500, // maxTokens
null, // maxCompletionTokens
1, // n
null, // presencePenalty
null, // responseFormat
null, // seed
null, // stop
false, // stream
null, // streamOptions
0.7, // temperature
null, // topP
null, // tools
null, // toolChoice
null, // parallelToolCalls
null, // user
null, // outputModalities
null, // outputAudio
null, // store
null, // metadata
null, // reasoningEffort
null, // verbosity
null, // webSearchOptions
null // serviceTier
);
ResponseEntity<ChatCompletion> response = api.chatCompletionEntity(request);
ChatCompletion completion = response.getBody();
String answer = completion.choices().get(0).message().content().toString();
System.out.println("Answer: " + answer);Streaming Chat:
// Streaming chat completion
var streamRequest = new ChatCompletionRequest(
List.of(
new ChatCompletionMessage(
"Write a short story",
ChatCompletionMessage.Role.USER,
null, null, null, null, null, null
)
),
OpenAiApi.ChatModel.GPT_4_O.getValue(),
null, null, null, null, 1000, null, 1, null, null, null, null,
true, // Enable streaming
new ChatCompletionRequest.StreamOptions(true), // Include usage
0.8, null, null, null, null, null, null, null, null, null, null, null, null, null
);
api.chatCompletionStream(streamRequest)
.subscribe(chunk -> {
var delta = chunk.choices().get(0).delta();
if (delta.content() != null) {
System.out.print(delta.content());
}
});Embeddings:
import org.springframework.ai.openai.api.OpenAiApi.EmbeddingRequest;
// Single text embedding
var embeddingRequest = new EmbeddingRequest<>(
"The quick brown fox jumps over the lazy dog",
OpenAiApi.EmbeddingModel.TEXT_EMBEDDING_3_SMALL.getValue(),
"float",
null, // Default dimensions
null // User
);
var embeddingResponse = api.embeddings(embeddingRequest);
float[] embedding = embeddingResponse.getBody().data().get(0).embedding();
// Multiple texts
var multiRequest = new EmbeddingRequest<>(
List.of("Text 1", "Text 2", "Text 3"),
OpenAiApi.EmbeddingModel.TEXT_EMBEDDING_3_LARGE.getValue(),
"float",
1024, // Reduced dimensions
"user-123"
);
var multiResponse = api.embeddings(multiRequest);REST API client for image generation.
/**
* Low-level OpenAI API client for image generation
*/
public class OpenAiImageApi {
/**
* Default image model constant (value: DALL_E_3)
*/
public static final String DEFAULT_IMAGE_MODEL = "dall-e-3";
/**
* Create a builder for configuring OpenAiImageApi
* @return Builder instance
*/
public static Builder builder();
/**
* Generate images from text prompt
* @param imageRequest Image generation request
* @return ResponseEntity with OpenAiImageResponse
*/
public ResponseEntity<OpenAiImageResponse> createImage(OpenAiImageRequest imageRequest);
}Builder Pattern:
public static class Builder {
public Builder apiKey(String apiKey);
public Builder baseUrl(String baseUrl);
public Builder organizationId(String organizationId);
public Builder projectId(String projectId);
public Builder headers(Map<String, String> headers);
public Builder restClient(RestClient restClient);
public OpenAiImageApi build();
}Usage Example:
import org.springframework.ai.openai.api.OpenAiImageApi;
import org.springframework.ai.openai.api.OpenAiImageApi.OpenAiImageRequest;
import org.springframework.ai.openai.api.OpenAiImageApi.OpenAiImageResponse;
// Create API client
var imageApi = OpenAiImageApi.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.build();
// Generate image
var request = new OpenAiImageRequest(
"A serene mountain landscape at sunset",
OpenAiImageApi.ImageModel.DALL_E_3.getValue(),
1, // n
"hd", // quality
"url", // responseFormat
"1024x1024", // size
"vivid", // style
null // user
);
var response = imageApi.createImage(request);
OpenAiImageResponse imageResponse = response.getBody();
imageResponse.data().forEach(data -> {
System.out.println("Image URL: " + data.url());
System.out.println("Revised prompt: " + data.revisedPrompt());
});REST API client for audio operations (speech synthesis and transcription).
/**
* Low-level OpenAI API client for audio operations
*/
public class OpenAiAudioApi {
/**
* Create a builder for configuring OpenAiAudioApi
* @return Builder instance
*/
public static Builder builder();
/**
* Generate speech audio from text
* @param speechRequest Speech generation request
* @return ResponseEntity with audio bytes
*/
public ResponseEntity<byte[]> createSpeech(SpeechRequest speechRequest);
/**
* Stream speech audio generation
* @param speechRequest Speech generation request
* @return Flux of ResponseEntity with audio chunks
*/
public Flux<ResponseEntity<byte[]>> stream(SpeechRequest speechRequest);
/**
* Transcribe audio to text
* @param transcriptionRequest Transcription request
* @param responseType Expected response type class
* @return ResponseEntity with transcription result
*/
public <T> ResponseEntity<T> createTranscription(
TranscriptionRequest transcriptionRequest,
Class<T> responseType
);
/**
* Translate audio to English text
* @param translationRequest Translation request
* @param responseType Expected response type class
* @return ResponseEntity with translation result
*/
public <T> ResponseEntity<T> createTranslation(
TranslationRequest translationRequest,
Class<T> responseType
);
}Builder Pattern:
public static class Builder {
public Builder apiKey(String apiKey);
public Builder baseUrl(String baseUrl);
public Builder organizationId(String organizationId);
public Builder projectId(String projectId);
public Builder headers(Map<String, String> headers);
public Builder restClient(RestClient restClient);
public OpenAiAudioApi build();
}Usage Example - Text-to-Speech:
import org.springframework.ai.openai.api.OpenAiAudioApi;
import org.springframework.ai.openai.api.OpenAiAudioApi.SpeechRequest;
import org.springframework.ai.openai.api.OpenAiAudioApi.SpeechRequest.Voice;
import org.springframework.ai.openai.api.OpenAiAudioApi.SpeechRequest.AudioResponseFormat;
// Create API client
var audioApi = OpenAiAudioApi.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.build();
// Generate speech
var speechRequest = new SpeechRequest(
OpenAiAudioApi.TtsModel.TTS_1_HD.getValue(),
"Hello, this is a test of text to speech",
Voice.NOVA,
AudioResponseFormat.MP3,
1.0 // speed
);
var response = audioApi.createSpeech(speechRequest);
byte[] audioData = response.getBody();
// Save to file
Files.write(Paths.get("speech.mp3"), audioData);Usage Example - Transcription:
import org.springframework.ai.openai.api.OpenAiAudioApi.TranscriptionRequest;
import org.springframework.ai.openai.api.OpenAiAudioApi.TranscriptResponseFormat;
import org.springframework.ai.openai.api.OpenAiAudioApi.StructuredResponse;
import org.springframework.core.io.FileSystemResource;
// Simple text transcription
var audioFile = new FileSystemResource("recording.mp3");
var textRequest = new TranscriptionRequest(
audioFile,
OpenAiAudioApi.WhisperModel.WHISPER_1.getValue(),
"en", // language
null, // prompt
TranscriptResponseFormat.TEXT,
0.0f, // temperature
null // timestampGranularities
);
var textResponse = audioApi.createTranscription(textRequest, String.class);
String transcript = textResponse.getBody();
System.out.println("Transcript: " + transcript);
// Verbose transcription with metadata
var verboseRequest = new TranscriptionRequest(
audioFile,
OpenAiAudioApi.WhisperModel.WHISPER_1.getValue(),
"en",
null,
TranscriptResponseFormat.VERBOSE_JSON,
0.0f,
List.of(TranscriptionRequest.GranularityType.WORD)
);
var verboseResponse = audioApi.createTranscription(verboseRequest, StructuredResponse.class);
StructuredResponse structured = verboseResponse.getBody();
System.out.println("Language: " + structured.language());
System.out.println("Duration: " + structured.duration() + " seconds");
System.out.println("Text: " + structured.text());
structured.words().forEach(word -> {
System.out.printf("%s [%.2f - %.2f]%n",
word.word(), word.start(), word.end());
});Usage Example - Translation:
import org.springframework.ai.openai.api.OpenAiAudioApi.TranslationRequest;
// Translate non-English audio to English
var audioFile = new FileSystemResource("spanish_audio.mp3");
var translationRequest = new TranslationRequest(
audioFile,
OpenAiAudioApi.WhisperModel.WHISPER_1.getValue(),
null, // prompt
"text", // responseFormat
0.0f // temperature
);
var translationResponse = audioApi.createTranslation(translationRequest, String.class);
String englishTranslation = translationResponse.getBody();
System.out.println("Translation: " + englishTranslation);REST API client for content moderation.
/**
* Low-level OpenAI API client for content moderation
*/
public class OpenAiModerationApi {
/**
* Default moderation model constant (value: "omni-moderation-latest")
*/
public static final String DEFAULT_MODERATION_MODEL = "omni-moderation-latest";
/**
* Create a builder for configuring OpenAiModerationApi
* @return Builder instance
*/
public static Builder builder();
/**
* Moderate content for policy violations
* @param moderationRequest Moderation request
* @return ResponseEntity with OpenAiModerationResponse
*/
public ResponseEntity<OpenAiModerationResponse> createModeration(
OpenAiModerationRequest moderationRequest
);
}Builder Pattern:
public static class Builder {
public Builder apiKey(String apiKey);
public Builder baseUrl(String baseUrl);
public Builder organizationId(String organizationId);
public Builder projectId(String projectId);
public Builder headers(Map<String, String> headers);
public Builder restClient(RestClient restClient);
public OpenAiModerationApi build();
}Usage Example:
import org.springframework.ai.openai.api.OpenAiModerationApi;
import org.springframework.ai.openai.api.OpenAiModerationApi.OpenAiModerationRequest;
import org.springframework.ai.openai.api.OpenAiModerationApi.OpenAiModerationResponse;
// Create API client
var moderationApi = OpenAiModerationApi.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.build();
// Moderate single text
var request = new OpenAiModerationRequest(
"I want to hurt someone",
"text-moderation-latest"
);
var response = moderationApi.createModeration(request);
OpenAiModerationResponse moderationResponse = response.getBody();
moderationResponse.results().forEach(result -> {
System.out.println("Flagged: " + result.flagged());
var categories = result.categories();
var scores = result.categoryScores();
System.out.println("Violence: " + categories.violence() +
" (score: " + scores.violence() + ")");
System.out.println("Hate: " + categories.hate() +
" (score: " + scores.hate() + ")");
});
// Moderate multiple texts
var multiRequest = new OpenAiModerationRequest(
List.of("Text 1", "Text 2", "Text 3"),
"text-moderation-stable"
);
var multiResponse = moderationApi.createModeration(multiRequest);REST API client for file management operations.
/**
* Low-level OpenAI API client for file operations
*/
public class OpenAiFileApi {
/**
* Create a builder for configuring OpenAiFileApi
* @return Builder instance
*/
public static Builder builder();
/**
* Upload a file to OpenAI
* @param uploadRequest File upload request
* @return ResponseEntity with FileObject
*/
public ResponseEntity<FileObject> uploadFile(UploadFileRequest uploadRequest);
/**
* List files
* @param listRequest List files request
* @return ResponseEntity with FileObjectResponse
*/
public ResponseEntity<FileObjectResponse> listFiles(ListFileRequest listRequest);
/**
* Retrieve file metadata
* @param fileId File ID
* @return ResponseEntity with FileObject
*/
public ResponseEntity<FileObject> retrieveFile(String fileId);
/**
* Delete a file
* @param fileId File ID
* @return ResponseEntity with DeleteFileResponse
*/
public ResponseEntity<DeleteFileResponse> deleteFile(String fileId);
/**
* Download file content
* @param fileId File ID
* @return ResponseEntity with file content as String
*/
public ResponseEntity<String> retrieveFileContent(String fileId);
}Builder Pattern:
public static class Builder {
public Builder apiKey(String apiKey);
public Builder baseUrl(String baseUrl);
public Builder organizationId(String organizationId);
public Builder projectId(String projectId);
public Builder headers(Map<String, String> headers);
public Builder restClient(RestClient restClient);
public OpenAiFileApi build();
}Usage Example:
import org.springframework.ai.openai.api.OpenAiFileApi;
import org.springframework.ai.openai.api.OpenAiFileApi.*;
import org.springframework.core.io.FileSystemResource;
// Create API client
var fileApi = OpenAiFileApi.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.build();
// Upload file
var uploadRequest = new UploadFileRequest(
new FileSystemResource("training_data.jsonl"),
Purpose.FINE_TUNE
);
var uploadResponse = fileApi.uploadFile(uploadRequest);
FileObject uploadedFile = uploadResponse.getBody();
System.out.println("File ID: " + uploadedFile.id());
System.out.println("Status: " + uploadedFile.status());
// List files
var listRequest = new ListFileRequest("fine-tune");
var listResponse = fileApi.listFiles(listRequest);
FileObjectResponse fileList = listResponse.getBody();
fileList.data().forEach(file -> {
System.out.println("File: " + file.filename());
System.out.println(" ID: " + file.id());
System.out.println(" Size: " + file.bytes() + " bytes");
System.out.println(" Purpose: " + file.purpose());
});
// Retrieve file metadata
var fileResponse = fileApi.retrieveFile(uploadedFile.id());
FileObject fileMetadata = fileResponse.getBody();
System.out.println("Filename: " + fileMetadata.filename());
System.out.println("Created at: " + fileMetadata.createdAt());
// Download file content
var contentResponse = fileApi.retrieveFileContent(uploadedFile.id());
String fileContent = contentResponse.getBody();
System.out.println("Content: " + fileContent);
// Delete file
var deleteResponse = fileApi.deleteFile(uploadedFile.id());
DeleteFileResponse deleteResult = deleteResponse.getBody();
System.out.println("Deleted: " + deleteResult.deleted());// Chat model enum
public enum ChatModel implements ChatModelDescription {
// Reasoning models
O4_MINI("o4-mini"),
O3("o3"),
O3_MINI("o3-mini"),
O1("o1"),
O1_MINI("o1-mini"),
O1_PRO("o1-pro"),
// Flagship models
GPT_4_1("gpt-4.1"),
GPT_5("gpt-5"),
GPT_5_MINI("gpt-5-mini"),
GPT_5_NANO("gpt-5-nano"),
GPT_5_CHAT_LATEST("gpt-5-chat-latest"),
GPT_4_O("gpt-4o"),
CHATGPT_4_O_LATEST("chatgpt-4o-latest"),
// Audio models
GPT_4_O_AUDIO_PREVIEW("gpt-4o-audio-preview"),
// Cost-optimized models
GPT_4_1_MINI("gpt-4.1-mini"),
GPT_4_1_NANO("gpt-4.1-nano"),
GPT_4_O_MINI("gpt-4o-mini"),
GPT_4_O_MINI_AUDIO_PREVIEW("gpt-4o-mini-audio-preview"),
// Realtime models
GPT_4O_REALTIME_PREVIEW("gpt-4o-realtime-preview"),
GPT_4O_MINI_REALTIME_PREVIEW("gpt-4o-mini-realtime-preview"),
// Older models
GPT_4_TURBO("gpt-4-turbo"),
GPT_4("gpt-4"),
GPT_3_5_TURBO("gpt-3.5-turbo"),
GPT_3_5_TURBO_INSTRUCT("gpt-3.5-turbo-instruct"),
// Search models
GPT_4_O_SEARCH_PREVIEW("gpt-4o-search-preview"),
GPT_4_O_MINI_SEARCH_PREVIEW("gpt-4o-mini-search-preview");
public String getValue();
}
// Finish reason enum
public enum ChatCompletionFinishReason {
STOP, // Natural completion
LENGTH, // Max tokens reached
TOOL_CALLS, // Tool call requested
CONTENT_FILTER, // Content filtered
FUNCTION_CALL // Function call (deprecated)
}
// Output modality enum
public enum OutputModality {
TEXT,
AUDIO
}
// Service tier enum
public enum ServiceTier {
AUTO,
DEFAULT
}// Embedding model enum
public enum EmbeddingModel {
TEXT_EMBEDDING_ADA_002("text-embedding-ada-002"),
TEXT_EMBEDDING_3_SMALL("text-embedding-3-small"),
TEXT_EMBEDDING_3_LARGE("text-embedding-3-large");
public String getValue();
}// Image model enum
public enum ImageModel {
DALL_E_2("dall-e-2"),
DALL_E_3("dall-e-3");
public String getValue();
}// TTS model enum
public enum TtsModel {
TTS_1("tts-1"),
TTS_1_HD("tts-1-hd"),
GPT_4_O_MINI_TTS("gpt-4o-mini-tts"),
GPT_4_O_TTS("gpt-4o-tts");
public String getValue();
}
// Whisper model enum
public enum WhisperModel {
WHISPER_1("whisper-1");
public String getValue();
}
// Transcription model enum
public enum TranscriptionModels implements ChatModelDescription {
WHISPER_1("whisper-1");
public String getValue();
}// File purpose enum
public enum Purpose {
FINE_TUNE("fine-tune"),
FINE_TUNE_RESULTS("fine-tune-results"),
ASSISTANTS("assistants"),
ASSISTANTS_OUTPUT("assistants_output"),
BATCH("batch"),
BATCH_OUTPUT("batch_output"),
VISION("vision");
public String getValue();
}// Add custom headers to requests
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
var headers = new LinkedMultiValueMap<String, String>();
headers.add("X-Custom-Header", "value");
headers.add("X-Request-ID", UUID.randomUUID().toString());
var response = api.chatCompletionEntity(request, headers);import org.springframework.ai.openai.api.common.OpenAiApiClientErrorException;
try {
var response = api.chatCompletionEntity(request);
// Process response
} catch (OpenAiApiClientErrorException e) {
System.err.println("API error: " + e.getMessage());
System.err.println("Status code: " + e.getStatusCode());
// Handle specific error types
}// Use with Azure OpenAI or other OpenAI-compatible services
var api = OpenAiApi.builder()
.apiKey(apiKey)
.baseUrl("https://your-custom-endpoint.com")
.build();// For multi-tenant or team-based usage
var api = OpenAiApi.builder()
.apiKey(apiKey)
.organizationId("org-xxx")
.projectId("proj-yyy")
.build();import org.springframework.web.client.RestClient;
// Configure custom RestClient for advanced scenarios
var restClient = RestClient.builder()
.requestInterceptor((request, body, execution) -> {
// Add custom logging, metrics, etc.
return execution.execute(request, body);
})
.build();
var api = OpenAiApi.builder()
.apiKey(apiKey)
.restClient(restClient)
.build();// Extract rate limit information from response headers
var response = api.chatCompletionEntity(request);
var headers = response.getHeaders();
var requestsLimit = headers.getFirst("x-ratelimit-limit-requests");
var requestsRemaining = headers.getFirst("x-ratelimit-remaining-requests");
var tokensLimit = headers.getFirst("x-ratelimit-limit-tokens");
var tokensRemaining = headers.getFirst("x-ratelimit-remaining-tokens");
System.out.println("Requests remaining: " + requestsRemaining);
System.out.println("Tokens remaining: " + tokensRemaining);import reactor.core.scheduler.Schedulers;
// Handle streaming with backpressure control
api.chatCompletionStream(request)
.publishOn(Schedulers.boundedElastic())
.onBackpressureBuffer(100)
.subscribe(
chunk -> processChunk(chunk),
error -> handleError(error),
() -> System.out.println("Stream complete")
);// Common API constants
public class OpenAiApiConstants {
/**
* Default OpenAI API base URL
*/
public static final String DEFAULT_BASE_URL = "https://api.openai.com";
/**
* Provider name identifier
*/
public static final String PROVIDER_NAME = "openai";
}The API clients are low-level and require more boilerplate. For most use cases, prefer:
OpenAiChatModel over OpenAiApiOpenAiEmbeddingModel over OpenAiApi.embeddings()OpenAiImageModel over OpenAiImageApiConfigure RestClient with proper connection pooling for production use:
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
var connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(100);
connectionManager.setDefaultMaxPerRoute(20);
var httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.build();
// Configure RestClient with custom HttpClientSet appropriate timeouts for API calls:
var restClient = RestClient.builder()
.requestFactory(clientHttpRequestFactory -> {
// Configure timeouts
clientHttpRequestFactory.setConnectTimeout(Duration.ofSeconds(10));
clientHttpRequestFactory.setReadTimeout(Duration.ofSeconds(60));
})
.build();Implement exponential backoff for transient failures:
import org.springframework.retry.support.RetryTemplate;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
var retryTemplate = RetryTemplate.builder()
.maxAttempts(3)
.exponentialBackoff(1000, 2.0, 10000)
.retryOn(OpenAiApiClientErrorException.class)
.build();
retryTemplate.execute(context -> {
return api.chatCompletionEntity(request);
});Never hardcode API keys. Use environment variables or secure configuration:
// Good - from environment
String apiKey = System.getenv("OPENAI_API_KEY");
// Good - from Spring configuration
@Value("${openai.api.key}")
private String apiKey;
// Bad - hardcoded
String apiKey = "sk-..."; // Never do this!Track token usage and costs:
var response = api.chatCompletionEntity(request);
var usage = response.getBody().usage();
System.out.println("Prompt tokens: " + usage.promptTokens());
System.out.println("Completion tokens: " + usage.completionTokens());
System.out.println("Total tokens: " + usage.totalTokens());
// Calculate approximate cost based on model pricingProperly handle errors in streaming responses:
api.chatCompletionStream(request)
.doOnError(error -> {
System.err.println("Stream error: " + error.getMessage());
// Implement fallback or retry logic
})
.retry(3)
.subscribe(chunk -> processChunk(chunk));Install with Tessl CLI
npx tessl i tessl/maven-org-springframework-ai--spring-ai-openai