Skip to content

Commit 82590ca

Browse files
committed
GH-3379: Spring Framework 7.x compatibility
- Use only Spring Framework APIs available both in 6.x and 7.x branches - Add constructors without logic to ``*Api` classes in `models` modules to simplify extensibility; effective final fields are marked as final - Kotlin 2.x support; use kotlin compiler version 2.2.21 Signed-off-by: Dmitry Bedrin <dmitry.bedrin@gmail.com>
1 parent 3cf3db1 commit 82590ca

File tree

154 files changed

+2158
-1431
lines changed

Some content is hidden

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

154 files changed

+2158
-1431
lines changed

.github/workflows/continuous-integration.yml

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,11 @@ on:
55
# Combined schedule covering both EST and CET working hours
66
# Morning/Early builds
77
- cron: '30 6 * * 1-5' # 7:30 AM CET / 1:30 AM EST
8-
- cron: '0 9 * * 1-5' # 10:00 AM CET / 4:00 AM EST
98
- cron: '30 11 * * 1-5' # 12:30 PM CET / 6:30 AM EST
109
# Midday builds
11-
- cron: '0 14 * * 1-5' # 3:00 PM CET / 9:00 AM EST
12-
- cron: '30 16 * * 1-5' # 5:30 PM CET / 11:30 AM EST
10+
- cron: '30 16 * * 1-5' # 7:30 PM CET / 11:30 AM EST
1311
# Afternoon/Evening builds
14-
- cron: '0 19 * * 1-5' # 8:00 PM CET / 2:00 PM EST
1512
- cron: '30 21 * * 1-5' # 10:30 PM CET / 4:30 PM EST
16-
- cron: '0 0 * * 2-6' # 1:00 AM CET / 7:00 PM EST (previous day)
17-
- cron: '30 2 * * 2-6' # 3:30 AM CET / 9:30 PM EST (previous day)
1813
workflow_dispatch:
1914
# Note: If push triggers are added in the future, they should include:
2015
# push:
@@ -29,9 +24,6 @@ jobs:
2924
name: Build branch
3025
runs-on: ubuntu-latest
3126
if: ${{ github.repository_owner == 'spring-projects' }}
32-
concurrency:
33-
group: continuous-integration-${{ github.ref }}
34-
cancel-in-progress: true # Skip if another build is running - next cron will trigger soon
3527
services:
3628
ollama:
3729
image: ollama/ollama:latest

.github/workflows/new-maven-central-release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
echo "${{ secrets.GPG_PRIVATE_KEY }}" > gpg.asc
2626
echo "${{ secrets.GPG_PASSPHRASE }}" | gpg --batch --yes --passphrase-fd 0 --import gpg.asc
2727
28-
- name: Release to Sonatype OSSRH
28+
- name: Release to Maven Central
2929
env:
3030
CENTRAL_TOKEN_USERNAME: ${{ secrets.CENTRAL_TOKEN_USERNAME }}
3131
CENTRAL_TOKEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }}

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ You can find more details in the [Reference Documentation](https://docs.spring.i
2626
- **Latest Models**: GPT-5, and other cutting-edge models for advanced AI applications.
2727
* Portable API support across AI providers for both synchronous and streaming options. Access to [model-specific features](https://docs.spring.io/spring-ai/reference/api/chatmodel.html#_chat_options) is also available.
2828
* [Structured Outputs](https://docs.spring.io/spring-ai/reference/api/structured-output-converter.html) - Mapping of AI Model output to POJOs.
29-
* Support for all major [Vector Database providers](https://docs.spring.io/spring-ai/reference/api/vectordbs.html) such as *Apache Cassandra, Azure Vector Search, Chroma, Elasticsearch, Milvus, MongoDB Atlas, MariaDB, Neo4j, Oracle, PostgreSQL/PGVector, PineCone, Qdrant, Redis, and Weaviate*.
29+
* Support for all major [Vector Database providers](https://docs.spring.io/spring-ai/reference/api/vectordbs.html) such as *Apache Cassandra, Azure Vector Search, Chroma, Elasticsearch, Milvus, MongoDB Atlas, MariaDB, Neo4j, Oracle, PostgreSQL/PGVector, Pinecone, Qdrant, Redis, and Weaviate*.
3030
* Portable API across Vector Store providers, including a novel SQL-like [metadata filter API](https://docs.spring.io/spring-ai/reference/api/vectordbs.html#metadata-filters).
3131
* [Tools/Function Calling](https://docs.spring.io/spring-ai/reference/api/tools.html) - permits the model to request the execution of client-side tools and functions, thereby accessing necessary real-time information as required.
3232
* [Observability](https://docs.spring.io/spring-ai/reference/observability/index.html) - Provides insights into AI-related operations.
@@ -156,4 +156,4 @@ define the source file coding standards we use along with some IDEA editor setti
156156

157157
## Contributing
158158

159-
Your contributions are always welcome! Please read the [contribution guidelines](CONTRIBUTING.adoc) first.
159+
Your contributions are always welcome! Please read the [contribution guidelines](CONTRIBUTING.adoc) first.

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

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

1919
import java.io.IOException;
20+
import java.net.URI;
2021
import java.nio.charset.StandardCharsets;
2122

2223
import org.slf4j.Logger;
@@ -30,6 +31,7 @@
3031
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3132
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3233
import org.springframework.context.annotation.Bean;
34+
import org.springframework.http.HttpMethod;
3335
import org.springframework.http.client.ClientHttpResponse;
3436
import org.springframework.lang.NonNull;
3537
import org.springframework.retry.RetryCallback;
@@ -87,6 +89,12 @@ public boolean hasError(@NonNull ClientHttpResponse response) throws IOException
8789
}
8890

8991
@Override
92+
public void handleError(@NonNull URI url, @NonNull HttpMethod method, @NonNull ClientHttpResponse response)
93+
throws IOException {
94+
handleError(response);
95+
}
96+
97+
@SuppressWarnings("removal")
9098
public void handleError(@NonNull ClientHttpResponse response) throws IOException {
9199
if (!response.getStatusCode().isError()) {
92100
return;

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfiguration.java

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerChangeNotificationProperties;
5252
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties;
5353
import org.springframework.beans.factory.ObjectProvider;
54+
import org.springframework.beans.factory.annotation.Qualifier;
5455
import org.springframework.boot.autoconfigure.AutoConfiguration;
5556
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
5657
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
@@ -91,30 +92,10 @@ public class McpServerAutoConfiguration {
9192

9293
private static final LogAccessor logger = new LogAccessor(McpServerAutoConfiguration.class);
9394

94-
/**
95-
* Creates a configured ObjectMapper for MCP server JSON serialization.
96-
* <p>
97-
* This ObjectMapper is specifically configured for MCP protocol compliance with:
98-
* <ul>
99-
* <li>Lenient deserialization that doesn't fail on unknown properties</li>
100-
* <li>Proper handling of empty beans during serialization</li>
101-
* <li>Exclusion of null values from JSON output</li>
102-
* <li>Standard Jackson modules for Java 8, JSR-310, and Kotlin support</li>
103-
* </ul>
104-
* <p>
105-
* This bean can be overridden by providing a custom ObjectMapper bean with the name
106-
* "mcpServerObjectMapper".
107-
* @return configured ObjectMapper instance for MCP server operations
108-
*/
109-
@Bean(name = "mcpServerObjectMapper")
110-
@ConditionalOnMissingBean(name = "mcpServerObjectMapper")
111-
public ObjectMapper mcpServerObjectMapper() {
112-
return McpServerObjectMapperFactory.createObjectMapper();
113-
}
114-
11595
@Bean
11696
@ConditionalOnMissingBean
117-
public McpServerTransportProviderBase stdioServerTransport(ObjectMapper mcpServerObjectMapper) {
97+
public McpServerTransportProviderBase stdioServerTransport(
98+
@Qualifier("mcpServerObjectMapper") ObjectMapper mcpServerObjectMapper) {
11899
return new StdioServerTransportProvider(new JacksonMcpJsonMapper(mcpServerObjectMapper));
119100
}
120101

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2025-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.mcp.server.common.autoconfigure;
18+
19+
import com.fasterxml.jackson.annotation.JsonInclude;
20+
import com.fasterxml.jackson.databind.DeserializationFeature;
21+
import com.fasterxml.jackson.databind.ObjectMapper;
22+
import com.fasterxml.jackson.databind.SerializationFeature;
23+
import com.fasterxml.jackson.databind.json.JsonMapper;
24+
import io.modelcontextprotocol.spec.McpSchema;
25+
26+
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties;
27+
import org.springframework.ai.util.JacksonUtils;
28+
import org.springframework.boot.autoconfigure.AutoConfiguration;
29+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
30+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
31+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
32+
import org.springframework.context.annotation.Bean;
33+
34+
@AutoConfiguration
35+
@ConditionalOnClass(McpSchema.class)
36+
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
37+
matchIfMissing = true)
38+
@ConditionalOnMissingBean(name = "mcpServerObjectMapper")
39+
public class McpServerObjectMapperAutoConfiguration {
40+
41+
/**
42+
* Creates a configured ObjectMapper for MCP server JSON serialization.
43+
* <p>
44+
* This ObjectMapper is specifically configured for MCP protocol compliance with:
45+
* <ul>
46+
* <li>Lenient deserialization that doesn't fail on unknown properties</li>
47+
* <li>Proper handling of empty beans during serialization</li>
48+
* <li>Exclusion of null values from JSON output</li>
49+
* <li>Standard Jackson modules for Java 8, JSR-310, and Kotlin support</li>
50+
* </ul>
51+
* <p>
52+
* This bean can be overridden by providing a custom ObjectMapper bean with the name
53+
* "mcpServerObjectMapper".
54+
* @return configured ObjectMapper instance for MCP server operations
55+
*/
56+
// NOTE: defaultCandidate=false prevents this MCP specific mapper from being injected
57+
// in code that doesn't explicitly qualify injection point by name.
58+
@Bean(name = "mcpServerObjectMapper", defaultCandidate = false)
59+
public ObjectMapper mcpServerObjectMapper() {
60+
return JsonMapper.builder()
61+
// Deserialization configuration
62+
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
63+
.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
64+
// Serialization configuration
65+
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
66+
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
67+
.serializationInclusion(JsonInclude.Include.NON_NULL)
68+
// Register standard Jackson modules (Jdk8, JavaTime, ParameterNames, Kotlin)
69+
.addModules(JacksonUtils.instantiateAvailableModules())
70+
.build();
71+
}
72+
73+
}

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerObjectMapperFactory.java

Lines changed: 0 additions & 105 deletions
This file was deleted.

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# limitations under the License.
1515
#
1616
org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration
17+
org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperAutoConfiguration
1718
org.springframework.ai.mcp.server.common.autoconfigure.ToolCallbackConverterAutoConfiguration
1819
org.springframework.ai.mcp.server.common.autoconfigure.McpServerStatelessAutoConfiguration
1920
org.springframework.ai.mcp.server.common.autoconfigure.StatelessToolCallbackConverterAutoConfiguration

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfigurationIT.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@
7272

7373
public class McpServerAutoConfigurationIT {
7474

75-
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(
76-
AutoConfigurations.of(McpServerAutoConfiguration.class, ToolCallbackConverterAutoConfiguration.class));
75+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
76+
.withConfiguration(AutoConfigurations.of(McpServerAutoConfiguration.class,
77+
McpServerObjectMapperAutoConfiguration.class, ToolCallbackConverterAutoConfiguration.class));
7778

7879
@Test
7980
void defaultConfiguration() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright 2025-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.mcp.server.common.autoconfigure;
18+
19+
import com.fasterxml.jackson.databind.ObjectMapper;
20+
import org.junit.jupiter.api.Test;
21+
22+
import org.springframework.boot.autoconfigure.AutoConfigurations;
23+
import org.springframework.boot.context.annotation.UserConfigurations;
24+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
25+
import org.springframework.context.annotation.Bean;
26+
import org.springframework.context.annotation.Configuration;
27+
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
30+
/**
31+
* Integration tests for {@link McpServerObjectMapperAutoConfiguration}
32+
*
33+
* @author guan xu
34+
*/
35+
public class McpServerObjectMapperAutoConfigurationIT {
36+
37+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
38+
.withConfiguration(AutoConfigurations.of(McpServerObjectMapperAutoConfiguration.class));
39+
40+
@Test
41+
void defaultMcpServerObjectMapper() {
42+
this.contextRunner.run(context -> {
43+
assertThat(context).hasSingleBean(ObjectMapper.class);
44+
assertThat(context).hasBean("mcpServerObjectMapper");
45+
});
46+
}
47+
48+
@Test
49+
void customizeMcpServerObjectMapper() {
50+
this.contextRunner.withConfiguration(UserConfigurations.of(TestConfig.class)).run(context -> {
51+
assertThat(context).hasSingleBean(ObjectMapper.class);
52+
assertThat(context).hasBean("mcpServerObjectMapper");
53+
54+
var mcpServerObjectMapper = context.getBean("mcpServerObjectMapper", ObjectMapper.class);
55+
var customizedMcpServerObjectMapper = context.getBean(TestConfig.class).mcpServerObjectMapper();
56+
assertThat(customizedMcpServerObjectMapper).isSameAs(mcpServerObjectMapper);
57+
});
58+
}
59+
60+
@Configuration
61+
static class TestConfig {
62+
63+
@Bean(name = "mcpServerObjectMapper")
64+
ObjectMapper mcpServerObjectMapper() {
65+
return new ObjectMapper();
66+
}
67+
68+
}
69+
70+
}

0 commit comments

Comments
 (0)