Integrate the OpenAI API in Java/Spring Boot: chat completions, streaming, function calling, and embeddings.
Published May 12, 2025
The OpenAI API gives programmatic access to GPT-4 and other models. Integrating it into a Spring Boot application enables AI-powered features like summarization, extraction, Q&A, and code generation.
<!-- pom.xml -->
<dependency>
<groupId>com.theokanning.openai-gpt3-java</groupId>
<artifactId>service</artifactId>
<version>0.18.2</version>
</dependency>
<!-- Or use Spring AI -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
# application.yml
openai:
api-key: ${OPENAI_API_KEY}
model: gpt-4o
max-tokens: 1000
temperature: 0.7
@Service
@RequiredArgsConstructor
public class AiService {
private final ChatClient chatClient;
public String summarize(String text) {
return chatClient.prompt()
.system("You are a technical writer. Summarize text concisely.")
.user("Summarize this in 3 bullet points:\n" + text)
.call()
.content();
}
public String generateCode(String description, String language) {
return chatClient.prompt()
.system("You are an expert " + language + " developer. Return ONLY code.")
.user(description)
.call()
.content();
}
}
public Flux<String> streamResponse(String userMessage) {
return chatClient.prompt()
.user(userMessage)
.stream()
.content(); // Flux<String> — each element is a token chunk
}
// In controller:
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> stream(@RequestParam String message) {
return aiService.streamResponse(message)
.map(chunk -> ServerSentEvent.builder(chunk).build());
}
Allow the model to call your Java methods:
@Bean
public List<FunctionCallback> tools() {
return List.of(
FunctionCallbackWrapper.builder(new GetWeatherFunction())
.withName("getCurrentWeather")
.withDescription("Get the current weather for a city")
.withResponseConverter(r -> r.toString())
.build()
);
}
record WeatherRequest(String city) {}
record WeatherResponse(String city, double temp, String condition) {}
@Component
public class GetWeatherFunction
implements Function<WeatherRequest, WeatherResponse> {
@Override
public WeatherResponse apply(WeatherRequest req) {
// Call actual weather API
return new WeatherResponse(req.city(), 22.5, "Sunny");
}
}
// Usage - model decides when to call the tool
String response = chatClient.prompt()
.user("What's the weather in Tokyo?")
.functions("getCurrentWeather")
.call()
.content();
@Service
@RequiredArgsConstructor
public class EmbeddingService {
private final EmbeddingClient embeddingClient;
public float[] embed(String text) {
EmbeddingResponse response = embeddingClient.embedForResponse(List.of(text));
return response.getResults().get(0).getOutput();
}
public double cosineSimilarity(float[] a, float[] b) {
double dot = 0, normA = 0, normB = 0;
for (int i = 0; i < a.length; i++) {
dot += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
}
}
try {
String result = chatClient.prompt().user(message).call().content();
} catch (OpenAiHttpException e) {
if (e.statusCode == 429) {
// Rate limited — retry with exponential backoff
} else if (e.statusCode == 400) {
// Invalid request — check prompt length, content policy
}
}