Spring AI: Building Intelligent Applications with Spring Boot
Introduction
Artificial Intelligence is no longer a futuristic concept—it's a practical tool for building smarter applications. Spring AI, the newest addition to the Spring ecosystem, makes it incredibly easy to integrate AI capabilities into your Spring Boot applications. Whether you want to build chatbots, process natural language, generate images, or create intelligent search features, Spring AI provides a unified framework for working with various AI models and APIs.
In this comprehensive guide, we'll explore Spring AI from the ground up, building practical applications that leverage AI capabilities without requiring deep machine learning expertise.
What is Spring AI?
Spring AI is a Spring project designed to simplify AI integration in Java applications. It provides:
- Unified API across different AI providers (OpenAI, Azure OpenAI, Ollama, etc.)
- Model-agnostic abstractions so you can switch AI providers without rewriting code
- Integration with Spring Boot for seamless dependency injection and configuration
- Chat interfaces, embeddings, and image generation capabilities
- RAG (Retrieval-Augmented Generation) support for context-aware AI
Think of it as JPA for AI—it abstracts away the differences between AI providers just like JPA abstracts database differences.
Getting Started: Basic Setup
1. Add Spring AI Dependencies
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
Or for other providers:
<!-- Azure OpenAI -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-azure-openai-spring-boot-starter</artifactId>
</dependency>
<!-- Ollama (Local models) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
2. Configure Your API Key
application.properties:
spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.chat.options.model=gpt-4
spring.ai.openai.chat.options.temperature=0.7
3. Create Your First AI Service
@Service
public class AIAssistant {
private final ChatClient chatClient;
public AIAssistant(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
public String askQuestion(String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}
}
That's it! You now have an AI service ready to use.
Building Chat Applications
Simple Chat Response
@RestController
@RequestMapping("/api/ai")
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@PostMapping("/chat")
public String chat(@RequestBody String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
}
Usage:
curl -X POST http://localhost:8080/api/ai/chat \
-H "Content-Type: application/json" \
-d '{"message": "Explain Spring AI in one sentence"}'
Advanced Chat with System Prompts
public String getCodeReview(String code) {
return chatClient.prompt()
.system("You are an expert Java code reviewer. Provide constructive feedback.")
.user("Review this code: " + code)
.call()
.content();
}
Multi-turn Conversations
@Service
public class ConversationService {
private final ChatClient chatClient;
private final List<Message> conversationHistory = new ArrayList<>();
public String chat(String userMessage) {
conversationHistory.add(new UserMessage(userMessage));
var response = chatClient.prompt()
.messages(conversationHistory)
.call()
.getResult();
conversationHistory.add(response.getOutput());
return response.getOutput().getContent();
}
}
Working with Embeddings
Embeddings convert text into numerical vectors, enabling semantic search and similarity comparisons.
@Service
public class DocumentSearchService {
private final EmbeddingClient embeddingClient;
private final VectorStore vectorStore;
public void indexDocument(String id, String content) {
Embedding embedding = embeddingClient.embed(content);
vectorStore.add(new Document(content, Map.of("id", id)));
}
public List<Document> searchSimilar(String query, int limit) {
List<Document> results = vectorStore.similaritySearch(
SearchRequest.query(query).withTopK(limit)
);
return results;
}
}
Building RAG (Retrieval-Augmented Generation) Applications
RAG combines retrieval with generation—you retrieve relevant documents and pass them to the AI model for context-aware responses.
@Service
public class RAGService {
private final ChatClient chatClient;
private final VectorStore vectorStore;
public String answerQuestion(String question) {
// Step 1: Retrieve relevant documents
List<Document> context = vectorStore.similaritySearch(
SearchRequest.query(question).withTopK(3)
);
// Step 2: Build context string
String contextString = context.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n---\n"));
// Step 3: Ask AI with context
return chatClient.prompt()
.system("Use the provided context to answer the user's question.\n" +
"Context:\n" + contextString)
.user(question)
.call()
.content();
}
}
Real-world example: Document-based Q&A
@Service
public class DocumentQAService {
// ... setup code ...
public void loadDocuments(String filePath) {
// Load and index documents
ResourceReader reader = new ResourceReader();
Document doc = reader.read(filePath);
vectorStore.add(doc);
}
public String askAboutDocuments(String question) {
return ragService.answerQuestion(question);
// Returns answers based on indexed documents, not general knowledge
}
}
Image Generation
Spring AI supports image generation with providers like OpenAI's DALL-E.
@Service
public class ImageGenerationService {
private final ImageClient imageClient;
public byte[] generateImage(String prompt) {
ImageResponse response = imageClient.call(
new ImagePrompt(prompt,
ImageOptionsBuilder.builder()
.model("dall-e-3")
.quality("hd")
.size("1024x1024")
.build())
);
return response.getResult().getOutput().b64_json.getBytes();
}
}
Function Calling (Tool Use)
Allow AI models to call your Java functions for more intelligent interactions.
@Service
public class AIWithTools {
private final ChatClient chatClient;
public String processWithTools(String userRequest) {
return chatClient.prompt()
.functions(
"getWeather", // Function name
"getStockPrice", // Another function
"queryDatabase" // And another
)
.user(userRequest)
.call()
.content();
}
@Bean
public Function<WeatherRequest, String> getWeather() {
return request -> {
// Fetch real weather data
return "Sunny, 72°F";
};
}
@Bean
public Function<StockRequest, String> getStockPrice() {
return request -> {
// Fetch real stock data
return "AAPL: $150.25";
};
}
}
Real-World Example: Intelligent Code Documentation Generator
@Service
public class CodeDocService {
private final ChatClient chatClient;
public String generateDocumentation(String sourceCode) {
return chatClient.prompt()
.system("""
You are an expert Java developer and technical writer.
Generate clear, concise JavaDoc-style documentation.
Include examples where appropriate.
""")
.user("Generate documentation for this code:\n" + sourceCode)
.call()
.content();
}
public String explainComplexLogic(String code) {
return chatClient.prompt()
.system("Explain complex code logic in simple terms for junior developers.")
.user("Explain this: " + code)
.call()
.content();
}
public String suggestImprovements(String code) {
return chatClient.prompt()
.system("Suggest performance and best practice improvements for Java code.")
.user("Review and suggest improvements: " + code)
.call()
.content();
}
}
Error Handling and Resilience
@Service
public class ResilientAIService {
private final ChatClient chatClient;
private final CircuitBreaker circuitBreaker;
public String askWithRetry(String question) {
try {
return circuitBreaker.executeSupplier(() ->
chatClient.prompt()
.user(question)
.call()
.content()
);
} catch (Exception e) {
logger.error("AI service failed", e);
return "I apologize, I couldn't process that request. Please try again.";
}
}
}
Performance Considerations
Caching AI Responses:
@Service
@CacheConfig(cacheNames = "aiResponses")
public class CachedAIService {
@Cacheable(key = "#question")
public String ask(String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}
}
Streaming for Long Responses:
public Flux<String> askStreaming(String question) {
return chatClient.prompt()
.user(question)
.stream()
.chatResponse()
.flatMapMany(response ->
Flux.fromIterable(response.getResults())
)
.map(r -> r.getOutput().getContent());
}
Testing Spring AI Applications
@SpringBootTest
class AIServiceTest {
@MockBean
private ChatClient.Builder chatClientBuilder;
@Autowired
private ChatClient chatClient;
@Test
void testChatResponse() {
String expectedResponse = "Spring AI makes AI integration easy!";
when(chatClient.prompt()
.user(anyString())
.call()
.content()).thenReturn(expectedResponse);
String response = chatClient.prompt()
.user("Tell me about Spring AI")
.call()
.content();
assertEquals(expectedResponse, response);
}
}
Switching AI Providers (The Power of Abstraction)
One of Spring AI's biggest advantages is switching providers without changing your application code:
// Same code works with different providers
// Just change application.properties:
// Using OpenAI
spring.ai.openai.api-key=${OPENAI_KEY}
spring.ai.openai.chat.options.model=gpt-4
// Switch to Azure without changing code:
spring.ai.azure.openai.api-key=${AZURE_KEY}
spring.ai.azure.openai.chat.options.model=gpt-4
// Or use local Ollama:
spring.ai.ollama.base-url=http://localhost:11434
spring.ai.ollama.chat.options.model=llama2
Best Practices
- Use System Prompts Effectively - Set the context and tone for AI responses
- Implement Rate Limiting - AI APIs can be expensive; protect against excessive usage
- Cache Responses - Store common questions and answers
- Monitor Token Usage - Track API calls to manage costs
- Validate AI Output - Don't blindly trust AI responses; validate and verify
- Use Streaming for Long Responses - Improve perceived performance
- Implement Error Handling - AI services can fail; have graceful fallbacks
Common Pitfalls to Avoid
- ❌ Exposing API keys in code (use environment variables)
- ❌ Trusting AI output without verification
- ❌ No error handling for API failures
- ❌ Ignoring token/cost limits
- ❌ Over-relying on AI for critical decisions
- ❌ No caching (expensive and slow)
Conclusion
Spring AI democratizes AI integration in Java applications. Whether you're building chatbots, intelligent search, code analysis tools, or RAG-powered applications, Spring AI provides a clean, Spring-idiomatic way to do it.
The framework handles the complexity of working with different AI providers while letting you focus on building amazing features. Start small with a simple chat interface, then gradually add more sophisticated capabilities like embeddings, function calling, and RAG.
The future of applications is intelligent applications—and with Spring AI, that future is now.
Resources
- Spring AI Official Documentation
- OpenAI API Documentation
- Spring AI GitHub Repository
- RAG Pattern Explained
Next Steps
- Create a simple Spring Boot app with Spring AI
- Experiment with different prompts
- Integrate embeddings for semantic search
- Build a RAG application with your own documents
- Deploy to production with proper monitoring
Top comments (0)