Java has dominated enterprise backends for two decades. The AI wave doesn't change that — it creates a problem: most AI tooling is Python-first. Spring AI solves this by bringing LLM integration, vector stores, and RAG pipelines into the familiar Spring Boot ecosystem. Here's how to use it properly in a production enterprise context.
What Spring AI Actually Is
Spring AI is a Spring Framework project (not a third-party library) that provides:
- A unified abstraction over LLM providers (OpenAI, Anthropic, Azure OpenAI, Ollama, and more)
- Built-in support for vector stores (pgvector, Pinecone, Redis, Chroma, Weaviate)
- Prompt templates with variable interpolation
- A ChatClient and EmbeddingClient with Spring-idiomatic APIs
- First-class support for Retrieval Augmented Generation (RAG)
The key insight: you write code against Spring AI's interfaces, not against OpenAI's SDK. Switching from GPT-4o to Claude Sonnet is a configuration change, not a rewrite.
Setting Up Spring AI
Add the Spring AI BOM to your pom.xml:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
</dependencies>
Configure in application.yml:
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4o
temperature: 0.7
max-tokens: 2048
ChatClient: Your Primary Interface
The ChatClient is the main entry point for LLM interactions. In Spring AI 1.0+, it has a fluent builder API:
@Service
public class AIAssistantService {
private final ChatClient chatClient;
public AIAssistantService(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("You are a senior software engineering assistant. " +
"Provide concise, production-ready code with explanations.")
.build();
}
public String generateResponse(String userQuery) {
return chatClient.prompt()
.user(userQuery)
.call()
.content();
}
public Flux<String> generateStreamingResponse(String userQuery) {
return chatClient.prompt()
.user(userQuery)
.stream()
.content();
}
}
The defaultSystem() call sets a persistent system prompt for every request through this client. Create separate ChatClient instances for different AI roles in your application.