一句话总结:本文提供Spring Boot调用Claude API的完整代码,覆盖同步调用、SSE流式输出、多轮对话,可直接复制运行。
一、Claude vs OpenAI:为什么选Claude?

Claude特别适合:长文档分析、复杂代码生成、需要严格遵循格式的场景。
二、依赖引入
xml
<!-- pom.xml -->
<dependencies>
<!-- Spring Boot WebFlux(用于WebClient) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- 可选:Spring AI Anthropic(如果官方支持) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-anthropic-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Lombok(简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
三、配置文件
yaml
# application.yml
anthropic:
api-key: ${ANTHROPIC_API_KEY}
base-url: https://api.anthropic.com
version: 2023-06-01 # API版本
model: claude-3-5-sonnet-20241022 # 最新模型
max-tokens: 4096
temperature: 0.7
# 超时配置
spring:
webflux:
client:
connect-timeout: 30000
read-timeout: 60000
四、核心代码
4.1 配置类:ClaudeClientConfig
kotlin
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
@Configuration
public class ClaudeClientConfig {
@Value("${anthropic.api-key}")
private String apiKey;
@Value("${anthropic.base-url:https://api.anthropic.com}")
private String baseUrl;
@Value("${anthropic.version:2023-06-01}")
private String apiVersion;
@Bean
public WebClient claudeWebClient() {
return WebClient.builder()
.baseUrl(baseUrl)
.defaultHeader("x-api-key", apiKey)
.defaultHeader("anthropic-version", apiVersion)
.defaultHeader("Content-Type", "application/json")
.build();
}
}
4.2 同步调用:ClaudeChatService
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import lombok.Data;
import java.util.List;
@Service
public class ClaudeChatService {
@Autowired
private WebClient claudeWebClient;
@Value("${anthropic.model:claude-3-5-sonnet-20241022}")
private String model;
@Value("${anthropic.max-tokens:4096}")
private int maxTokens;
/**
* 同步单轮对话
*/
public String chat(String message) {
ClaudeRequest request = new ClaudeRequest();
request.setModel(model);
request.setMaxTokens(maxTokens);
request.setMessages(List.of(
new Message("user", message)
));
ClaudeResponse response = claudeWebClient.post()
.uri("/v1/messages")
.bodyValue(request)
.retrieve()
.bodyToMono(ClaudeResponse.class)
.block(); // 同步阻塞
return extractContent(response);
}
/**
* 同步多轮对话
*/
public String chatWithHistory(List<Message> history, String newMessage) {
history.add(new Message("user", newMessage));
ClaudeRequest request = new ClaudeRequest();
request.setModel(model);
request.setMaxTokens(maxTokens);
request.setMessages(history);
ClaudeResponse response = claudeWebClient.post()
.uri("/v1/messages")
.bodyValue(request)
.retrieve()
.bodyToMono(ClaudeResponse.class)
.block();
// 把AI回复加入历史
history.add(new Message("assistant", extractContent(response)));
return extractContent(response);
}
private String extractContent(ClaudeResponse response) {
if (response == null || response.getContent() == null || response.getContent().isEmpty()) {
return "";
}
return response.getContent().get(0).getText();
}
// DTO类
@Data
public static class ClaudeRequest {
private String model;
private int maxTokens;
private List<Message> messages;
}
@Data
public static class Message {
private String role; // "user" or "assistant"
private String content;
public Message(String role, String content) {
this.role = role;
this.content = content;
}
}
@Data
public static class ClaudeResponse {
private String id;
private String model;
private List<ContentBlock> content;
private Usage usage;
}
@Data
public static class ContentBlock {
private String type; // "text"
private String text;
}
@Data
public static class Usage {
private int inputTokens;
private int outputTokens;
}
}
4.3 流式调用:ClaudeStreamService(SSE)
arduino
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import java.util.List;
@Service
public class ClaudeStreamService {
@Autowired
private WebClient claudeWebClient;
@Autowired
private ObjectMapper objectMapper;
/**
* 流式对话(SSE)
* 返回Flux<String>,每个元素是一个token片段
*/
public Flux<String> chatStream(String message) {
ClaudeRequest request = new ClaudeRequest();
request.setModel("claude-3-5-sonnet-20241022");
request.setMaxTokens(4096);
request.setMessages(List.of(new Message("user", message)));
request.setStream(true); // 关键:开启流式
return claudeWebClient.post()
.uri("/v1/messages")
.bodyValue(request)
.retrieve()
.bodyToFlux(String.class) // 按行读取SSE数据
.filter(line -> line.startsWith("data:")) // 只处理data:行
.map(line -> line.substring(5).trim()) // 去掉"data: "前缀
.filter(data -> !data.equals("[DONE]")) // 过滤结束标记
.map(this::extractDeltaText)
.filter(text -> text != null && !text.isEmpty());
}
/**
* 从SSE事件中提取文本增量
*/
private String extractDeltaText(String jsonData) {
try {
StreamEvent event = objectMapper.readValue(jsonData, StreamEvent.class);
if ("content_block_delta".equals(event.getType()) && event.getDelta() != null) {
return event.getDelta().getText();
}
return "";
} catch (Exception e) {
return "";
}
}
// 流式事件DTO
@Data
public static class StreamEvent {
private String type; // "content_block_delta", "message_start", etc.
private String index;
private Delta delta;
}
@Data
public static class Delta {
private String type; // "text_delta"
private String text; // 实际的文本增量
}
// 复用ClaudeChatService的DTO
@Data
public static class ClaudeRequest {
private String model;
private int maxTokens;
private List<ClaudeChatService.Message> messages;
private boolean stream;
}
@Data
public static class Message {
private String role;
private String content;
public Message(String role, String content) {
this.role = role;
this.content = content;
}
}
}
4.4 Controller层:REST API暴露
typescript
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/api/claude")
public class ClaudeController {
@Autowired
private ClaudeChatService chatService;
@Autowired
private ClaudeStreamService streamService;
/**
* 同步对话
*/
@PostMapping("/chat")
public Mono<String> chat(@RequestBody ChatRequest request) {
return Mono.just(chatService.chat(request.getMessage()));
}
/**
* 流式对话(SSE)
*/
@PostMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chatStream(@RequestBody ChatRequest request) {
return streamService.chatStream(request.getMessage());
}
/**
* 多轮对话(带历史)
*/
private final List<ClaudeChatService.Message> conversationHistory = new ArrayList<>();
@PostMapping("/chat/history")
public Mono<String> chatWithHistory(@RequestBody ChatRequest request) {
String response = chatService.chatWithHistory(conversationHistory, request.getMessage());
return Mono.just(response);
}
@PostMapping("/chat/history/clear")
public Mono<String> clearHistory() {
conversationHistory.clear();
return Mono.just("历史记录已清空");
}
@Data
public static class ChatRequest {
private String message;
}
}
五、测试验证
5.1 同步调用测试
bash
curl -X POST http://localhost:8080/api/claude/chat -H "Content-Type: application/json" -d '{"message": "用Java写一个单例模式,要求线程安全"}'
5.2 流式调用测试(SSE)
bash
curl -X POST http://localhost:8080/api/claude/chat/stream -H "Content-Type: application/json" -d '{"message": "讲一个程序员笑话"}'
# 输出:
# data: 有
# data: 一
# data: 个
# data: 程序员...
5.3 前端EventSource消费
ini
const eventSource = new EventSource('/api/claude/chat/stream', {
method: 'POST',
body: JSON.stringify({message: '你好'})
});
eventSource.onmessage = (event) => {
console.log('收到:', event.data);
appendToUI(event.data); // 追加到页面
};
eventSource.onerror = () => {
console.log('连接结束');
eventSource.close();
};
六、与OpenAI对比
typescript
@Service
public class AIChatService {
@Autowired
private ClaudeChatService claudeService;
// 可切换的AI Provider
public String chat(String provider, String message) {
switch (provider) {
case "claude":
return claudeService.chat(message);
case "openai":
// return openAIService.chat(message);
default:
return claudeService.chat(message);
}
}
}
七、常见问题
7.1 403 Forbidden
Claude API需要申请访问权限,新账户可能无法直接调用。
解决:在Anthropic官网申请API访问,或使用第三方代理。
7.2 流式输出乱码
SSE事件格式与OpenAI不同,注意解析content_block_delta事件。
7.3 上下文长度超限
Claude 3.5支持200K tokens,但超过后会被截断。
解决:实现滑动窗口历史管理,只保留最近N轮对话。
八、完整项目结构
bash
claude-java-demo/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/claude/
│ │ │ ├── ClaudeJavaDemoApplication.java
│ │ │ ├── config/
│ │ │ │ └── ClaudeClientConfig.java
│ │ │ ├── service/
│ │ │ │ ├── ClaudeChatService.java
│ │ │ │ └── ClaudeStreamService.java
│ │ │ └── controller/
│ │ │ └── ClaudeController.java
│ │ └── resources/
│ │ └── application.yml
│ └── test/
│ └── java/
│ └── ClaudeChatServiceTest.java
👤 关于作者
JavaAgent架构师 --- 十年Java分布式架构老兵,专注AI Agent企业级落地。
主导过数字员工、SOP智能引擎等项目,开发过RPC框架、消息中间件、ORM框架。
📚 正在输出三个专栏:
- 《前端AI工程化》--- SSE/流式渲染/Function Calling/企业级架构
- 《浩哥学JavaAI:35岁架构师转型日记》--- 探索AI副业变现、让AI提升能力
让Java开发者不转Python,让前端工程师掌握AI核心竞争力。
📮 关注专栏 |🔔 点赞收藏 |💬 评论区见