Add AI to a Spring Boot Project in a Few Lines with Spring AI 2.0

This guide walks through integrating Spring AI 2.0 into a Spring Boot application, covering Maven dependency setup, model configuration, ChatClient usage for synchronous and streaming calls, Redis‑backed chat memory for multi‑turn conversations, and testing with Postman and a UI component.

SpringMeng
SpringMeng
SpringMeng
Add AI to a Spring Boot Project in a Few Lines with Spring AI 2.0

Spring AI Overview

Multi‑model, multi‑vendor support – a single codebase works with OpenAI, Deepseek, Anthropic, Ollama and other models for chat, text‑to‑image, speech‑to‑text, etc.

ChatClient streaming API – provides call() for synchronous calls and stream() for SSE‑style streaming.

Advisors API – encapsulates RAG, conversation memory, safety filters and other patterns as reusable interceptor chains.

Chat Memory – built‑in, Redis, JDBC and other back‑ends automatically manage multi‑turn conversation history.

RAG – one‑line vector‑store integration lets a model read your documents.

Tool Calling – annotate Java methods with @Tool so the model can invoke real APIs.

Project Integration

Step 1 – Add the Spring AI BOM to pom.xml (version 2.0.0) to manage consistent dependency versions.
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.ai</groupId>
      <artifactId>spring-ai-bom</artifactId>
      <version>${spring-ai.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
Step 2 – Add the OpenAI‑compatible model starter and the WebFlux starter (required for SSE).
<!-- Spring AI OpenAI Starter -->
<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>

<!-- SpringBoot WebFlux starter (provides Flux/Mono) -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Step 3 – Configure the model in application.yaml . The example uses Deepseek‑v4‑pro via the OpenAI‑compatible endpoint.
spring:
  ai:
    openai:
      # Access Deepseek through OpenAI‑compatible API
      base-url: https://api.deepseek.com
      api-key: ${DEEPSEEK_API_KEY}
    chat:
      model: deepseek-v4-pro
Step 4 – Define a configuration class that creates a ChatClient bean.
@Configuration
public class SpringAIConfig {
    @Bean
    public ChatClient chatClient(ChatClient.Builder builder) {
        return builder.build();
    }
}

Chat Service Implementation

Create ChatController with two endpoints: a synchronous /call and a streaming /stream that returns MediaType.TEXT_EVENT_STREAM_VALUE .
@RestController
@RequestMapping("/chat")
@RequiredArgsConstructor
public class ChatController {
    private final ChatClient chatClient;

    @PostMapping("/call")
    public String call(@RequestParam String question, @RequestParam String conversationId) {
        return this.chatClient.prompt()
                .user(question)
                .call()
                .content();
    }

    @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<ChatEventDto> stream(@RequestParam String question, @RequestParam String conversationId) {
        return this.chatClient.prompt()
                .user(question)
                .stream()
                .content()
                .map(content -> ChatEventDto.builder()
                        .eventType(ChatEventType.DATA.getValue())
                        .eventData(content)
                        .build())
                .concatWith(Flux.just(ChatEventDto.builder()
                        .eventType(ChatEventType.STOP.getValue())
                        .build()));
    }
}
Define ChatEventDto to wrap SSE messages.
@Data @Builder @NoArgsConstructor @AllArgsConstructor @Schema(title = "ChatEventDto", description = "SSE session event result")
public class ChatEventDto {
    @Schema(description = "Event type: 1001‑data, 1002‑stop, 1003‑parameter")
    private Integer eventType;
    @Schema(description = "Message content")
    private Object eventData;
}

Conversation Memory with Redis

Run Redis‑Stack (required for vector and JSON capabilities) via Docker.
docker run --name redis-stack \
  -p 6379:6379 \
  -p 8001:8001 \
  -d redis/redis-stack:7.4.0-v8
Add the Redis chat‑memory starter.
<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-starter-model-chat-memory-repository-redis</artifactId>
</dependency>
Configure Redis connection details in application.yaml .
spring:
  ai:
    chat:
      memory:
        redis:
          host: 192.168.3.101
          port: 6379
          time-to-live: 24h
          key-prefix: spring-chat
Define beans for RedisChatMemoryRepository and a ChatMemory that uses a MessageWindowChatMemory with a maximum of 100 messages.
@Configuration
public class SpringAIConfig {
    @Value("${spring.ai.chat.memory.redis.host}")
    private String redisHost;
    @Value("${spring.ai.chat.memory.redis.port}")
    private int redisPort;

    @Bean
    public RedisChatMemoryRepository redisChatMemoryRepository() {
        RedisClient redisClient = RedisClient.builder()
                .hostAndPort(redisHost, redisPort)
                .build();
        return RedisChatMemoryRepository.builder()
                .jedisClient(redisClient)
                .build();
    }

    @Bean
    public ChatMemory chatMemory(RedisChatMemoryRepository repository) {
        return MessageWindowChatMemory.builder()
                .chatMemoryRepository(repository)
                .maxMessages(100)
                .build();
    }
}
Inject ChatMemory into ChatController and use it to store and retrieve conversation history, enabling true multi‑turn dialogue.
private final ChatMemory chatMemory;

// Retrieve history
List<Message> history = chatMemory.get(conversationId);
// Save user message
chatMemory.add(conversationId, List.of(new UserMessage(question)));
// After model response, save assistant message
chatMemory.add(conversationId, List.of(new AssistantMessage(response)));

// History endpoint
@GetMapping("/history")
public CommonResult<List<Map<String, Object>>> history(@RequestParam String conversationId) {
    var result = chatMemory.get(conversationId).stream()
            .map(msg -> Map.of(
                    "role", msg.getMessageType().name(),
                    "content", msg.getText()))
            .collect(Collectors.toList());
    return CommonResult.success(result);
}

// Clear history endpoint
@PostMapping("/clearHistory")
public CommonResult<Void> clearHistory(@RequestParam String conversationId) {
    chatMemory.clear(conversationId);
    return CommonResult.success(null);
}

Streaming with Memory

In the streaming endpoint, accumulate the full assistant reply and store it after the stream completes.
@PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ChatEventDto> stream(@RequestParam String question, @RequestParam String conversationId) {
    List<Message> history = chatMemory.get(conversationId);
    chatMemory.add(conversationId, List.of(new UserMessage(question)));
    StringBuilder fullResponse = new StringBuilder();
    return this.chatClient.prompt()
            .messages(history)
            .user(question)
            .stream()
            .content()
            .map(content -> {
                fullResponse.append(content);
                return ChatEventDto.builder()
                        .eventType(ChatEventType.DATA.getValue())
                        .eventData(content)
                        .build();
            })
            .concatWith(Flux.just(ChatEventDto.builder()
                    .eventType(ChatEventType.STOP.getValue())
                    .build()))
            .doOnComplete(() -> {
                if (!fullResponse.isEmpty()) {
                    chatMemory.add(conversationId, List.of(new AssistantMessage(fullResponse.toString())));
                }
            });
}

Testing the Service

Using Postman, a call to /call returns a single JSON response. A call to /stream returns incremental SSE messages. After enabling Redis memory, repeated queries reference earlier turns, confirming multi‑turn conversation capability.

Source Code

All code and configuration are available at https://github.com/macrozheng/spring-ai-examples

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaaiRedisSpring BootOpenAISpring AIChatbotsse
SpringMeng
Written by

SpringMeng

Focused on software development, sharing source code and tutorials for various systems.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.