Exploring the Java LLM Ecosystem: Build Your First AI Chat Application
This tutorial walks Java backend developers through the mature Java LLM ecosystem, comparing frameworks like Spring AI and LangChain4j, and demonstrates step‑by‑step how to create a Spring Boot application with a chat endpoint, streaming responses, and dynamic model switching among OpenAI, Tongyi Qwen, and Ollama.
1. Java LLM Ecosystem Overview
The article compares four Java‑centric LLM frameworks:
Spring AI – official Spring integration, high maturity, suited for Spring Boot users.
LangChain4j – active community, rich features, also high maturity.
Ollama Java – lightweight, runs local models, medium maturity.
DeepLearning4J – deep‑learning framework, lower maturity, mainly for model training.
2. Project Goal
Build a Java application that provides a chat API, supports streaming output, and can switch between OpenAI, Alibaba Tongyi Qwen, and local Ollama models through a unified Spring AI abstraction.
2.1 Spring AI Quick Start
Project creation
<!-- pom.xml -->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
<relativePath/>
</parent>
<groupId>com.teaching</groupId>
<artifactId>java-ai-agent</artifactId>
<version>1.0.0‑SNAPSHOT</version>
<properties>
<java.version>17</java.version>
<spring‑ai.version>1.0.0‑M4</spring‑ai.version>
</properties>
<repositories>
<repository>
<id>spring‑milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<dependencies>
<!-- Spring AI OpenAI starter -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring‑ai‑openai‑spring‑boot‑starter</artifactId>
<version>${spring‑ai.version}</version>
</dependency>
<!-- Spring Boot Web starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‑boot‑starter‑web</artifactId>
</dependency>
<!-- Lombok (optional) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>Configuration file
# application.yml
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
base-url: ${OPENAI_BASE_URL:https://api.openai.com/v1}
chat:
options:
model: ${MODEL_NAME:gpt-4}
temperature: 0.7
max-tokens: 2000
server:
port: 8080
logging:
level:
org.springframework.ai: DEBUGFirst AI chat endpoint
// ChatController.java
package com.teaching.ai.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/chat")
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@PostMapping("/simple")
public Map<String, String> simpleChat(@RequestBody Map<String, String> request) {
String message = request.get("message");
String response = chatClient.prompt(message).call().content();
Map<String, String> result = new HashMap<>();
result.put("response", response);
return result;
}
@GetMapping("/hello")
public Map<String, String> hello() {
return Map.of("message", "AI Agent service started");
}
}Application entry point
// AiApplication.java
package com.teaching.ai;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AiApplication {
public static void main(String[] args) {
SpringApplication.run(AiApplication.class, args);
System.out.println("✅ Java AI Agent started!");
System.out.println(" http://localhost:8080/api/chat/hello");
}
}3. Streaming Output
// StreamController.java
package com.teaching.ai.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import java.util.Map;
@RestController
@RequestMapping("/api/stream")
public class StreamController {
private final ChatClient chatClient;
public StreamController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@PostMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamChat(@RequestBody Map<String, String> request) {
String message = request.get("message");
return chatClient.prompt(message)
.stream()
.content()
.map(chunk -> "data: " + chunk + "
");
}
}4. Multi‑model Switching
Configuration for multiple models
# application.yml (additional sections)
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4
dashscope:
api-key: ${DASHSCOPE_API_KEY}
chat:
options:
model: qwen-maxModel router service
// ModelRouterService.java
package com.teaching.ai.service;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.dashscope.DashScopeChatModel;
import org.springframework.stereotype.Service;
@Service
public class ModelRouterService {
private final OpenAiChatModel openAiModel;
private final DashScopeChatModel dashScopeModel;
public ModelRouterService(OpenAiChatModel openAiModel, DashScopeChatModel dashScopeModel) {
this.openAiModel = openAiModel;
this.dashScopeModel = dashScopeModel;
}
public ChatModel selectModel(String modelType) {
return switch (modelType) {
case "openai" -> openAiModel;
case "qwen" -> dashScopeModel;
default -> openAiModel;
};
}
public String chat(String message, String modelType) {
ChatModel model = selectModel(modelType);
return model.call(message);
}
}5. Testing
# Test simple chat
curl -X POST http://localhost:8080/api/chat/simple \
-H "Content-Type: application/json" \
-d '{"message":"What is an AI Agent? Explain in one sentence"}'
# Test streaming output
curl -X POST http://localhost:8080/api/stream/chat \
-H "Content-Type: application/json" \
-d '{"message":"Tell me a joke"}'6. Common Issues
Version compatibility
Ensure you use Spring Boot 3.2.x together with Spring AI 1.0.0‑M4.
API‑key configuration
Store keys in environment variables instead of hard‑coding them, e.g. export OPENAI_API_KEY=your-key.
Frontend handling of SSE
// JavaScript example for Server‑Sent Events
const eventSource = new EventSource('/api/stream/chat');
eventSource.onmessage = (event) => {
console.log(event.data);
};7. Next Episode Preview
The upcoming article will dive deeper into Spring AI, covering advanced ChatClient usage, prompt‑template systems, output parsers, and function calling.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Coder Trainee
Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
