Skip to content

Commit d5e92be

Browse files
bedrintzolov
authored andcommitted
Upgrade to Spring Boot 4.0.0-RC2 (#4774)
Upgrade Spring AI to Spring Boot 4.0.0-RC2 following the https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide Major version upgrades: - Spring Boot: 3.5.7 → 4.0.0-RC2 - Kotlin: 1.9.25 → 2.2.21 (with Kotlin compiler 2.2.21) - Rest Assured: added BOM 5.5.6 - swagger-codegen-maven-plugin: 3.0.64 → 3.0.75 - Testcontainers: 1.20.4 → 2.0.1 - Updated Elasticsearch test container to 9.2.0 - Keep opensearch-testcontainers to 2.0.1 for Java 17 compatibility - Gemfire Testcontainers: 2.3.0 → 2.3.3 Spring Boot package reorganization: - MongoDB: o.s.b.autoconfigure.mongo → o.s.b.mongodb.autoconfigure - Data Redis: o.s.b.autoconfigure.data.redis → o.s.b.data.redis.autoconfigure - JDBC: o.s.b.autoconfigure.jdbc → o.s.b.jdbc.autoconfigure - Cassandra: o.s.b.autoconfigure.cassandra → o.s.b.cassandra.autoconfigure - Neo4j: o.s.b.autoconfigure.neo4j → o.s.b.neo4j.autoconfigure - Couchbase: o.s.b.autoconfigure.couchbase → o.s.b.couchbase.autoconfigure - RestClient: o.s.b.autoconfigure.web.client → o.s.b.restclient.autoconfigure - WebClient: o.s.b.autoconfigure.web.reactive → o.s.b.webclient.autoconfigure - Servlet: o.s.b.autoconfigure.web.servlet → o.s.b.web.server.autoconfigure.servlet Spring Framework 6.x/7.x compatibility: - Add constructors without logic to *Api classes for better extensibility Adopt new modular design of starters: - Added explicit spring-boot-starter-* dependencies (jdbc, cassandra, mongodb, neo4j, elasticsearch, etc.) - Added spring-boot-starter-restclient and spring-boot-starter-webclient where needed Use new Jackson 3.x API: - Updated import com.fasterxml.jackson.databind.json.JsonMapper - Modified ObjectMapper configurations for compatibility Migrated from Spring Retry to Spring Framework Retry: - org.springframework.retry → org.springframework.core.retry - Updated RetryTemplate, RetryPolicy, and RetryListener APIs - Replaced RetryCallback/RetryContext with new Retryable interface Property Mapper API changes: - Updated PropertyMapper null handling (changed from .whenNonNull() to direct usage) - Applied in Couchbase and Elasticsearch vector store configurations API changes: - Replaced MultiValueMap<String, String> with HttpHeaders for HTTP headers - Updated Elasticsearch client to use Rest5Client (from RestClient) - Standardized header handling across OpenAI, Anthropic, DeepSeek, MiniMax, ZhiPuAI, Mistral AI, and other APIs - Added overloaded constructors accepting RestClient/WebClient directly - Updated HttpHeaders methods (.containsKey() → .get()) Testcontainers artifact naming: - Standardized to prefixed format (e.g., junit-jupiter → testcontainers-junit-jupiter) - Updated across all modules: Cassandra, PostgreSQL, MongoDB, Neo4j, Milvus, Qdrant, etc. Additional improvements: - Updated Swagger codegen templates for Huggingface model to align with latest Spring Framework APIs - Added FailureDetectingExternalResource from old testcontainers versions for gemfire-testcontainers compatibility - Updated Kotlin nullable type handling in extensions (reified T → reified T : Any) - Added missing test dependencies (spring-boot-restclient-test, spring-boot-starter-jdbc-test) Resolves: #3379 Extends and replaces: #4771, #4681 Co-authored-by: Paul Bakker <[email protected]> Signed-off-by: Dmitry Bedrin <[email protected]> Signed-off-by: Christian Tzolov <[email protected]>
1 parent 10bc0a7 commit d5e92be

File tree

263 files changed

+2636
-1064
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

263 files changed

+2636
-1064
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Spring AI [![build status](https://github.com/spring-projects/spring-ai/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/spring-projects/spring-ai/actions/workflows/continuous-integration.yml) [![build status](https://github.com/spring-projects/spring-ai-integration-tests/actions/workflows/spring-ai-integration-tests.yml/badge.svg)](https://github.com/spring-projects/spring-ai-integration-tests/actions/workflows/spring-ai-integration-tests.yml)
22

3+
### Spring Boot Version Compatibility
4+
5+
> **Spring AI 2.x.x** ([main](https://github.com/spring-projects/spring-ai/tree/main) branch) - Spring Boot `4.x`
6+
>
7+
> **Spring AI 1.1.x** ([1.1.x](https://github.com/spring-projects/spring-ai/tree/1.1.x) branch) - Spring Boot `3.5.x`
8+
39

410
The Spring AI project provides a Spring-friendly API and abstractions for developing AI applications.
511

auto-configurations/common/spring-ai-autoconfigure-retry/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@
6161
<artifactId>spring-boot-starter-test</artifactId>
6262
<scope>test</scope>
6363
</dependency>
64+
<dependency>
65+
<groupId>org.springframework.boot</groupId>
66+
<artifactId>spring-boot-starter-restclient-test</artifactId>
67+
<scope>test</scope>
68+
</dependency>
6469

6570
<dependency>
6671
<groupId>org.mockito</groupId>

auto-configurations/common/spring-ai-autoconfigure-retry/src/main/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryAutoConfiguration.java

Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
package org.springframework.ai.retry.autoconfigure;
1818

1919
import java.io.IOException;
20+
import java.net.URI;
2021
import java.nio.charset.StandardCharsets;
22+
import java.util.concurrent.atomic.AtomicInteger;
2123

2224
import org.slf4j.Logger;
2325
import org.slf4j.LoggerFactory;
@@ -30,16 +32,15 @@
3032
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3133
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3234
import org.springframework.context.annotation.Bean;
35+
import org.springframework.core.retry.RetryListener;
36+
import org.springframework.core.retry.RetryPolicy;
37+
import org.springframework.core.retry.RetryTemplate;
38+
import org.springframework.core.retry.Retryable;
39+
import org.springframework.http.HttpMethod;
3340
import org.springframework.http.client.ClientHttpResponse;
3441
import org.springframework.lang.NonNull;
35-
import org.springframework.retry.RetryCallback;
36-
import org.springframework.retry.RetryContext;
37-
import org.springframework.retry.RetryListener;
38-
import org.springframework.retry.support.RetryTemplate;
39-
import org.springframework.retry.support.RetryTemplateBuilder;
4042
import org.springframework.util.CollectionUtils;
4143
import org.springframework.util.StreamUtils;
42-
import org.springframework.web.client.ResourceAccessException;
4344
import org.springframework.web.client.ResponseErrorHandler;
4445

4546
/**
@@ -61,35 +62,25 @@ public class SpringAiRetryAutoConfiguration {
6162
@Bean
6263
@ConditionalOnMissingBean
6364
public RetryTemplate retryTemplate(SpringAiRetryProperties properties) {
64-
RetryTemplateBuilder builder = RetryTemplate.builder()
65+
RetryPolicy retryPolicy = RetryPolicy.builder()
6566
.maxAttempts(properties.getMaxAttempts())
66-
.retryOn(TransientAiException.class)
67-
.retryOn(ResourceAccessException.class)
68-
.exponentialBackoff(properties.getBackoff().getInitialInterval(), properties.getBackoff().getMultiplier(),
69-
properties.getBackoff().getMaxInterval())
70-
.withListener(new RetryListener() {
71-
72-
@Override
73-
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
74-
Throwable throwable) {
75-
logger.warn("Retry error. Retry count: {}, Exception: {}", context.getRetryCount(),
76-
throwable.getMessage(), throwable);
77-
}
78-
});
79-
80-
// Optionally add WebFlux pre-response network errors if present
81-
try {
82-
Class<?> webClientRequestEx = Class
83-
.forName("org.springframework.web.reactive.function.client.WebClientRequestException");
84-
@SuppressWarnings("unchecked")
85-
Class<? extends Throwable> exClass = (Class<? extends Throwable>) webClientRequestEx;
86-
builder.retryOn(exClass);
87-
}
88-
catch (ClassNotFoundException ignore) {
89-
// WebFlux not on classpath; skip
90-
}
91-
92-
return builder.build();
67+
.includes(TransientAiException.class)
68+
.delay(properties.getBackoff().getInitialInterval())
69+
.multiplier(properties.getBackoff().getMultiplier())
70+
.maxDelay(properties.getBackoff().getMaxInterval())
71+
.build();
72+
73+
RetryTemplate retryTemplate = new RetryTemplate(retryPolicy);
74+
retryTemplate.setRetryListener(new RetryListener() {
75+
private final AtomicInteger retryCount = new AtomicInteger(0);
76+
77+
@Override
78+
public void onRetryFailure(RetryPolicy policy, Retryable<?> retryable, Throwable throwable) {
79+
int currentRetries = this.retryCount.incrementAndGet();
80+
logger.warn("Retry error. Retry count:{}", currentRetries, throwable);
81+
}
82+
});
83+
return retryTemplate;
9384
}
9485

9586
@Bean
@@ -104,6 +95,12 @@ public boolean hasError(@NonNull ClientHttpResponse response) throws IOException
10495
}
10596

10697
@Override
98+
public void handleError(@NonNull URI url, @NonNull HttpMethod method, @NonNull ClientHttpResponse response)
99+
throws IOException {
100+
handleError(response);
101+
}
102+
103+
@SuppressWarnings("removal")
107104
public void handleError(@NonNull ClientHttpResponse response) throws IOException {
108105
if (!response.getStatusCode().isError()) {
109106
return;

auto-configurations/common/spring-ai-autoconfigure-retry/src/test/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryAutoConfigurationIT.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
import org.junit.jupiter.api.Test;
2020

2121
import org.springframework.boot.autoconfigure.AutoConfigurations;
22-
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
22+
import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration;
2323
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
24-
import org.springframework.retry.support.RetryTemplate;
24+
import org.springframework.core.retry.RetryTemplate;
2525
import org.springframework.web.client.ResponseErrorHandler;
2626

2727
import static org.assertj.core.api.Assertions.assertThat;

auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282

8383
<dependency>
8484
<groupId>org.testcontainers</groupId>
85-
<artifactId>junit-jupiter</artifactId>
85+
<artifactId>testcontainers-junit-jupiter</artifactId>
8686
<scope>test</scope>
8787
</dependency>
8888
</dependencies>

auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-webflux/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888

8989
<dependency>
9090
<groupId>org.testcontainers</groupId>
91-
<artifactId>junit-jupiter</artifactId>
91+
<artifactId>testcontainers-junit-jupiter</artifactId>
9292
<scope>test</scope>
9393
</dependency>
9494

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/pom.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,17 @@
9595
<scope>test</scope>
9696
</dependency>
9797

98+
<dependency>
99+
<groupId>org.springframework.boot</groupId>
100+
<artifactId>spring-boot-starter-restclient</artifactId>
101+
<scope>test</scope>
102+
</dependency>
103+
<dependency>
104+
<groupId>org.springframework.boot</groupId>
105+
<artifactId>spring-boot-starter-webclient</artifactId>
106+
<scope>test</scope>
107+
</dependency>
108+
98109
<dependency>
99110
<groupId>org.springframework.ai</groupId>
100111
<artifactId>spring-ai-autoconfigure-model-anthropic</artifactId>

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfigurationTests.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package org.springframework.ai.mcp.server.autoconfigure;
1818

19-
import com.fasterxml.jackson.databind.ObjectMapper;
19+
import com.fasterxml.jackson.databind.json.JsonMapper;
2020
import io.modelcontextprotocol.server.transport.WebFluxSseServerTransportProvider;
2121
import org.junit.jupiter.api.Test;
2222

@@ -43,10 +43,11 @@ void shouldConfigureWebFluxTransportWithCustomObjectMapper() {
4343
assertThat(context).hasSingleBean(RouterFunction.class);
4444
assertThat(context).hasSingleBean(McpServerProperties.class);
4545

46-
ObjectMapper objectMapper = context.getBean("mcpServerObjectMapper", ObjectMapper.class);
46+
JsonMapper jsonMapper = context.getBean("mcpServerObjectMapper", JsonMapper.class);
4747

48-
// Verify that the ObjectMapper is configured to ignore unknown properties
49-
assertThat(objectMapper.getDeserializationConfig()
48+
// Verify that the JsonMapper is configured to ignore unknown properties
49+
50+
assertThat(jsonMapper
5051
.isEnabled(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse();
5152

5253
// Test with a JSON payload containing unknown fields
@@ -61,7 +62,7 @@ void shouldConfigureWebFluxTransportWithCustomObjectMapper() {
6162
// CHECKSTYLE:ON
6263

6364
// This should not throw an exception
64-
TestMessage message = objectMapper.readValue(jsonWithUnknownField, TestMessage.class);
65+
TestMessage message = jsonMapper.readValue(jsonWithUnknownField, TestMessage.class);
6566
assertThat(message.getName()).isEqualTo("test");
6667
});
6768
}

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpToolCallProviderCachingIT.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@
4848
import org.springframework.ai.tool.ToolCallbackProvider;
4949
import org.springframework.ai.tool.function.FunctionToolCallback;
5050
import org.springframework.boot.autoconfigure.AutoConfigurations;
51-
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
52-
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
51+
import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration;
5352
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
53+
import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration;
5454
import org.springframework.context.ApplicationContext;
5555
import org.springframework.context.annotation.Bean;
5656
import org.springframework.http.server.reactive.HttpHandler;

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpToolCallbackParameterlessToolIT.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@
4848
import org.springframework.ai.tool.ToolCallbackProvider;
4949
import org.springframework.ai.tool.definition.ToolDefinition;
5050
import org.springframework.boot.autoconfigure.AutoConfigurations;
51-
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
52-
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
51+
import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration;
5352
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
53+
import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration;
5454
import org.springframework.context.ApplicationContext;
5555
import org.springframework.http.server.reactive.HttpHandler;
5656
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;

0 commit comments

Comments
 (0)