feat: upgrade to Spring Boot 4.1.1 and Spring AI 2.0.1 - #23
Open
adityamparikh wants to merge 15 commits into
Open
adityamparikh wants to merge 15 commits into
adityamparikh wants to merge 15 commits into
Conversation
adityamparikh
marked this pull request as draft
December 16, 2025 22:10
Contributor
|
Thanks for the update on this one... We will merge when we get an official Spring AI 2.0 release! |
3 tasks
epugh
pushed a commit
that referenced
this pull request
Mar 9, 2026
* feat: implement observability with OpenTelemetry and Micrometer, add logging configuration, and enable tracing with annotation support * test(observability): implement distributed tracing tests for Spring Boot 3.5 Add comprehensive distributed tracing test suite using SimpleTracer from micrometer-tracing-test library. This is the Spring Boot 3-native approach for testing observability without requiring external infrastructure. Key changes: - Add DistributedTracingTest with 6 passing tests for @observed methods - Add OpenTelemetryTestConfiguration providing SimpleTracer as @primary bean - Add OtlpExportIntegrationTest (disabled due to Jetty dependency issue) - Add LgtmAssertions and TraceAssertions utility classes - Add micrometer-tracing-bridge-otel to bridge Observation API to OpenTelemetry - Add spring-boot-starter-aop for @observed annotation support - Add test dependencies: micrometer-tracing-test, awaitility, Jetty modules Test results: - DistributedTracingTest: 6/6 tests passing - Spans successfully captured from @observed annotations - Build: SUCCESS (219 tests passing, 0 failures) Spring Boot 3.5 uses Micrometer Observation → Micrometer Tracing → OpenTelemetry bridge, which differs from Spring Boot 4's direct OpenTelemetry integration. Adapted from PR #23 (Spring Boot 4 implementation) with modifications for Spring Boot 3.5 architecture and APIs. --------- Signed-off-by: adityamparikh <aditya.m.parikh@gmail.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Contributor
|
Looks like you have some conflicts.. I think you need to update from main to pick up the new ci stuff? |
adityamparikh
force-pushed
the
sb4
branch
2 times, most recently
from
May 6, 2026 14:01
6bc312d to
6427366
Compare
adityamparikh
marked this pull request as ready for review
June 14, 2026 00:16
adityamparikh
force-pushed
the
sb4
branch
2 times, most recently
from
June 14, 2026 03:40
9bbfae5 to
857fa32
Compare
Contributor
Author
|
Superseded. This comment described an earlier state of the branch (milestone versions, the 2026-08 rebase, or intermediate fixes). The PR description now reflects the current diff against |
Migrate the Solr MCP server from Spring Boot 3.5.x / Spring AI 1.1.x to Spring Boot 4.1.0 and Spring AI 2.0.0 GA. Highlights: - Jackson 3: tools.jackson.databind replaces com.fasterxml.jackson.databind (annotations stay in com.fasterxml.jackson.annotation); the MCP SDK uses the mcp-json-jackson3 module. JacksonException replaces IOException/ JsonProcessingException; JsonNode.properties()/asString() replace fields()/asText(). - MCP annotations: package moved from org.springaicommunity.mcp.annotation to org.springframework.ai.mcp.annotation (now part of Spring AI core). - Modular starters: spring-boot-starter-web -> -webmvc + -json; explicit spring-ai-autoconfigure-mcp-server-common (no longer pulled transitively after Spring AI 2.0.0-M7); modular test starters (actuator / opentelemetry / webmvc). - Observability: spring-boot-starter-opentelemetry (SB4 idiomatic) for traces, metrics, and OTLP log export, replacing the manual OTel BOM + micrometer-tracing-bridge-otel approach; spring-boot-starter-aspectj for @observed; OpenTelemetry logback appender installed programmatically via InstallOpenTelemetryAppender; management.opentelemetry.* OTLP properties. - Testcontainers 2.x module/package renames. - JSpecify is built into Spring Boot 4, so the explicit dependency is dropped. - GraalVM native: AOT NullAway exclusions for generated sources, JUnit-platform build-time init, DefaultMetaProvider + Solr API model reflection hints, and per-profile AOT documentation; dependency-resolution alignment for opentelemetry-proto and opentelemetry-api-incubator against the Spring Boot 4.1.0 BOM. Both Spring Boot 4.1.0 and Spring AI 2.0.0 are GA on Maven Central, so no milestone repositories are required. Scope is limited to the Spring Boot 4 / Spring AI 2.0 upgrade and its required changes; unrelated CI, Solr-version-matrix, and Solr 10 metrics changes are intentionally excluded. Signed-off-by: adityamparikh <aditya.m.parikh@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… JSON test to Jackson 3 Resolving PR #23 (sb4 -> main) onto current main surfaced two build breaks that the conflict resolution had to fix: 1. SBOM completeness gate failed. `generateBinaryLicense` compares the CycloneDX SBOM against the shipped `productionRuntimeClasspath`, but `cyclonedxBom` was never configured for it. Spring Boot's `CycloneDxPluginAction` only auto-configures the cyclonedx plugin version it recognizes (3.x for Spring Boot 4.1.0); we stay pinned to 2.4.1 because cyclonedx 3.x fails at configuration time on Gradle 9.4.1 (variant-mutation conflict on `:cyclonedxDirectBom`). With 2.4.1 unrecognized, the task fell back to plugin defaults: it wrote `build/reports/bom.json` (not `application.cdx.json`) and scanned the wrong configuration set (stale Jackson 2, none of the Spring Boot 4 modular jars), so the gate reported ~30 bundled deps "absent from the SBOM". Configure the task explicitly: `outputName = "application.cdx"` and `includeConfigs = [productionRuntimeClasspath]`. 2. `JsonResponseParserContentTypesTest` (added on main via #90) imported the Jackson 2 `com.fasterxml.jackson.databind.ObjectMapper`, but SB4 migrated `JsonResponseParser` to Jackson 3. Use `tools.jackson.databind.json.JsonMapper` (the project's Jackson 3 mapper-construction pattern) instead. Update the AGENTS.md SBOM Architecture note to match the explicit wiring. `./gradlew build` is green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: adityamparikh <aditya.m.parikh@gmail.com>
The lgtm service published Grafana on "3000:3000" (all interfaces) while setting GF_AUTH_ANONYMOUS_ORG_ROLE=Admin, so any host that could reach the developer's machine had unauthenticated admin access to the observability stack - no credentials, no prompt. Verified against the running container: - Old config: anonymous POST /api/folders -> HTTP 200, folder created. - New config: same request -> HTTP 403 "folders:create" denied. - Grafana answers on 127.0.0.1:3000 but the LAN address refuses the connection. Defaults are now loopback-only and read-only. Both are overridable for the cases that genuinely need them, so the previous behaviour stays available but has to be asked for: GF_ANON_ROLE=Admin docker compose up -d lgtm # editable dashboards GRAFANA_BIND=0.0.0.0 docker compose up -d lgtm # expose on a trusted LAN The OTLP receivers (4317/4318) are bound the same way via OTLP_BIND. The application posts telemetry to http://localhost:4318 by default, so the documented workflow is unaffected. Reported by a CodeRabbit review of #23. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
The lgtm service tracked grafana/otel-lgtm:latest, so a rebuild could pull a
different observability stack without any change to this repo. Pin 0.30.0
(current release as of 2026-07-31).
Verified against the pinned image rather than assumed:
- Provisioned Grafana datasources are Loki, Prometheus (default), Pyroscope
and Tempo. There is no Mimir datasource, and the image ships a prometheus
binary and prometheus.yaml with no mimir equivalent.
- OTLP ingestion works end to end on this tag: POST /v1/metrics returned 200
and Prometheus then answered probe_counter_total{service_name=
"solr-mcp-probe"} = 7.
So the docs were sending readers to a datasource that does not exist -
"Select Mimir as the datasource" could not be followed. Corrected across
compose.yaml and dev-docs/Observability.md, and added Pyroscope, which the
stack provides but the docs never mentioned. The architecture diagram was
redrawn (Prometheus is wider than Mimir) and all rows re-aligned to 81
columns.
The LGTM table keeps its "M is for Mimir" row - that is what the acronym
stands for - but now says the image actually ships Prometheus.
Reported by a CodeRabbit review of #23.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
Rebasing the Spring Boot 4 / Spring AI 2.0 branch onto current main applied cleanly at the textual level but left three test-side breaks that only compiling and running the suite surfaces. 1. `SearchServiceIntegrationTest` and `IndexingServiceIntegrationTest` failed to compile. Main still uses the wildcard `import static org.junit.jupiter.api.Assertions.*`, which this branch had expanded to explicit imports. Git merged the explicit list, so the assertions main added since the fork point (apache#166 `assertThrows`, apache#165 `assertFalse`) had no import. Add the two missing static imports rather than reintroducing the wildcard. 2. `SolrNativeHintsTest.registersDefaultMetaProviderConstructorHint` (added on main via apache#172) asserts a hint for `org.springaicommunity.mcp.context.DefaultMetaProvider`. Spring AI 2.x relocated that class to `org.springframework.ai.mcp.annotation.context.DefaultMetaProvider`, which the Registrar already registers. Because `registerTypeIfPresent` silently no-ops for a class that is absent from the classpath, the legacy coordinate registers nothing here and the assertion failed. Point the test at the coordinate that Spring AI 2.x actually ships; the legacy registration stays in the Registrar defensively. `./gradlew build` is green on JDK 25: 372 tests, 0 failures, 0 errors, 7 skipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
…line
0.0.6 is built against the Spring Boot 3 line: its POM declares
spring-core 6.2.9 and spring-security-{web,config,core,oauth2-*} 6.5.2,
and it predates mcp-core 2.x entirely. This branch ships Spring Framework
7.0.8 and Spring Security 7.1.0 via the Spring Boot 4.1.0 BOM, so the
0.0.6 bytecode was running against a Security major it never saw.
0.1.14 is the current release of the same library on the Boot 4 line:
spring-core 7.0.8, spring-security-* 7.1.0, mcp-core 2.0.0 — matching
exactly what Spring Boot 4.1.0 and Spring AI 2.0.0 put on the classpath.
The upgrade is a version-catalog change only; McpServerOAuth2Configurer's
API in HttpSecurityConfiguration is unchanged.
Note on verification: no automated test in this repo exercises
McpServerOAuth2Configurer on any version. It is wired only when
OAUTH2_ISSUER_URI is set (HttpSecurityConfiguration#secured), and
DockerImageHttpIntegrationTest deliberately runs without an issuer since
it has no IdP. That coverage gap is pre-existing and worth closing
separately.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
This was referenced Aug 19, 2026
Addresses review findings on this PR. Versions: 4.1.0 -> 4.1.1 and 2.0.0 -> 2.0.1, both verified as current against maven-metadata.xml rather than a search index. The PR described 4.1.0/2.0.0 as "the latest GA releases", which was true when written. Observability docs were the substantive problem. This PR moves OTLP export from gRPC on 4317 to HTTP on 4318 with a separate full URL per signal, which silently redefines OTEL_TRACES_URL: it used to be a base endpoint and is now a complete path ending in /v1/traces. docs/observability.md -- the user-facing guide README links to -- still documented the removed otel.exporter.otlp.* properties and told readers to export a :4317 base URL. Following it after this merged would not error; traces would just stop arriving. - docs/observability.md: real property names, corrected production example, a variable table, and an explicit upgrade warning about the changed semantics - dev-docs/Observability.md: added here, so it now states its relationship to the user-facing guide instead of silently competing with it; defaults filled in for the three OTLP variables - README.md: names all four OTEL variables - AGENTS.md: version references, and the OTEL variables added to Key Configuration Also removed a broken image reference in dev-docs/Observability.md (images/grafana-traces.png does not exist in the repo); the prose that follows already describes the trace breakdown in full. build.gradle.kts: documented why spring-boot-starter-mongodb is excluded from the Spring AI docker-compose module, and linked the CycloneDX 2.4.1 pin to apache#186 so it gets revisited rather than becoming permanent. Verified: ./gradlew build green, 372 tests / 0 failures (matching main's count), and nativeTest green at 227 passing / 0 failed -- unchanged by the version bump. Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
Spring AI 2.0.1 depends on io.modelcontextprotocol.sdk:mcp 2.0.0 directly and spring-ai-bom does not manage the SDK, so 2.0.0 arrived as a plain transitive. Import mcp-bom ahead of spring-ai-bom (the dependency-management plugin uses Maven "first declaration wins" semantics) so the whole SDK moves to 2.0.1 as one coherent set rather than pinning mcp-core and leaving mcp-json-jackson3 behind. 2.0.1 fixes three things on code paths this server actually uses: - error handling when a handler is not registered on a stateless server, which is the HTTP profile's configuration (spring.ai.mcp.server.protocol=stateless) - empty-completion handling when no handler matches a valid ref; this server exposes five @McpComplete handlers - bounded reads on the HTTP and STDIO transports Verified: clean build green, 372 tests / 0 failures / 7 skipped, unchanged from the pre-bump baseline. The shipped SBOM reports mcp, mcp-core and mcp-json-jackson3 all at 2.0.1. Drop the pin once a Spring AI release ships 2.0.1 or later itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T8Gq4z6jhiP9XGJujEu5ju Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
MemberCategory.DECLARED_FIELDS is deprecated and marked for removal in Spring Framework 7.0, in favour of ACCESS_DECLARED_FIELDS. Spring Framework 7 renamed the AOT field categories to disambiguate intent (ACCESS_*, INTROSPECT_*, INVOKE_*); the javadoc documents ACCESS_DECLARED_FIELDS as having the same semantics, so this is a drop-in rename and not a change to hint behaviour. This never surfaced because javac collapses deprecation and removal into a single "Some input files use or override a deprecated API" summary line. It is only visible under -Xlint:deprecation,removal, where [removal] is the tag that turns into a hard compile error on a future Spring upgrade. Verified: - ./gradlew clean build: 372 tests / 0 failures / 7 skipped, and the two [removal] warnings are gone - ./gradlew nativeTest -Pnative: 227 successful / 0 failed / 142 skipped, an exact match to the baseline -- the change is in the native-hints path, so the native run is the one that matters here Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T8Gq4z6jhiP9XGJujEu5ju Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
Clears the build's only Gradle deprecation:
The ReportingExtension.file(String) method has been deprecated.
This is scheduled to be removed in Gradle 10.
at org.nosphere.apache.rat.RatTask.<init>(RatTask.kt:138)
The call is inside the RAT plugin's own task constructor, so it fires on
every build regardless of which tasks are requested, and nothing in this
repository's build scripts can avoid it. 0.8.2 no longer uses the removed
API.
Verified: ./gradlew rat --warning-mode all goes from 1 deprecation to 0, and
./gradlew clean build --warning-mode all reports 0 Gradle deprecations with
372 tests / 0 failures / 7 skipped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T8Gq4z6jhiP9XGJujEu5ju
Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
…artifacts The 2.4.1 pin existed because cyclonedx 3.x once failed at configuration time on Gradle 9.4.1 (a variant-mutation conflict on :cyclonedxDirectBom). That is fixed as of 3.4.1, which configures cleanly. Closes the follow-up tracked in apache#186. The pin had a cost that was not obvious. Spring Boot's CyclonedxPluginAction bails when it does not recognise the plugin version, and jar-embedding lives inside that same action -- so the pinned build shipped no SBOM at all: cyclonedx 2.4.1: bootJar has no META-INF/sbom/ entry and no Sbom-Format or Sbom-Location manifest headers cyclonedx 3.4.1: META-INF/sbom/application.cdx.json present (141 components) plus both manifest headers Nothing served /actuator/sbom/application, and since the Jib JVM image and both Paketo native images package bootJar contents, none of the three published images carried an SBOM either. `./gradlew cyclonedxBom` kept writing a report under build/reports/, which is why this went unnoticed. Scope still has to be configured by hand, but on a different task. cyclonedx 3.x splits the work: cyclonedxDirectBom (CyclonedxDirectTask) resolves the graph and owns includeConfigs, while cyclonedxBom only aggregates. Left at defaults the direct task scans every configuration, adding ~100 test/build-only components (JUnit, AssertJ, ByteBuddy, docker-java, JaCoCo, Error Prone, NullAway) that are not in the fat jar. Setting includeConfigs on cyclonedxDirectBom restores exact parity with the 2.4.1 output: 141 components, zero difference in either direction. That scoping is load-bearing and unguarded -- the LICENSE appendix filters to shipped coordinates and the completeness gate only fails on missing entries, never extra ones, so an over-broad SBOM would ship silently and read as false-positive CVEs in scanners. AGENTS.md now says so explicitly, and its SBOM section is corrected: it described embedding and actuator serving that were not actually happening. Also drops the manual outputName wiring, now auto-configured by Spring Boot at build/reports/cyclonedx/application.cdx.json, and points the license-notice plugin at that path. Verified: ./gradlew clean build --warning-mode all green, 372 tests / 0 failures / 7 skipped, 0 deprecations; bootJar carries LICENSE, NOTICE and a 141-component SBOM with no test-only entries. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T8Gq4z6jhiP9XGJujEu5ju Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
Resolves the conflicts between main and the Spring Boot 4 / Spring AI 2 upgrade, keeping the Boot 4 line throughout: - build.gradle.kts, libs.versions.toml: keep spring-boot-starter-opentelemetry, mcp-server-security 0.1.14 and the MCP SDK 2.0.1 pin; add CommonMark for markdown indexing (apache#144). JSpecify stays transitive (built into Boot 4). - Service classes: keep the Spring AI 2 annotation package (org.springframework.ai.mcp.annotation) and take the SLF4J loggers from apache#110. - JsonResponseParser: Jackson 3 API (properties(), JacksonException) combined with the facet-path tracking from apache#175. - JsonUtils, IndexingServiceIntegrationTest and the new JsonResponseParserTest: Jackson 3 (tools.jackson) instead of com.fasterxml; MarkdownDocumentCreator wired into the indexing test. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HQhKFCmP75K71UV7CiYTqB Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
application-stdio.properties set logging.pattern.console= (empty) as a "second line of defence" for keeping stdout clean. Spring Boot copies that value into the JVM-wide CONSOLE_LOG_PATTERN system property (first writer wins), and logback rejects an empty pattern with "Empty or null pattern" rather than silencing output. On Boot 3.5 this never surfaced: the second Spring context in a test JVM found logback already initialised and kept the first context's appender-less stdio configuration. Spring Framework 7 pauses a test's ApplicationContext on context switch, which stops Boot's logging lifecycle bean and makes the next context re-initialise logback under its own profile. Any stdio-profile test running before DistributedTracingTest therefore broke its http-profile context with a logback configuration error. The stdio profile stays silent because logback-spring.xml declares no appenders for it, so the property is dropped. LoggingConfigurationTest now fails if an empty pattern is reintroduced, and the docs no longer describe it as a defence. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HQhKFCmP75K71UV7CiYTqB Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
Takes the single-logback-spring.xml convention from apache#193 and carries the Boot 4 empty-console-pattern fix across it: - AGENTS.md, docs/security/stdio.md: keep apache#193's wording but describe the empty logging.pattern.console as something the stdio profile must not set, with the Spring Framework 7 context-pausing rationale. - LoggingConfigurationTest moved to the top-level package in apache#193; the stdioProfileDoesNotExportAnEmptyConsolePattern guard is re-homed there and the old config/ copy is dropped. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HQhKFCmP75K71UV7CiYTqB Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
Contributor
Author
|
Verified the current head
For comparison |
- application-stdio.properties excluded the Boot 3 security auto-config class names, which Boot 4 ignores; replaced with the 4.1 names verified in spring-boot-security-4.1.1.jar. - application-http.properties overrode spring.application.name for one profile only, and still exposed /actuator/prometheus after the registry was removed; both gone, with the three docs that curl'd the endpoint. - DistributedTracingTest disabled export through the deprecated management.otlp.tracing.endpoint; it now uses management.tracing.export.otlp.enabled=false. - InMemoryTracingTestConfiguration was referenced by nothing. - compose.yaml keeps the org.springframework.boot.ignore label on the LGTM service, as on main, so Boot's compose support leaves it alone. - Unreferenced catalog entries (instrumentation BOM version, tracing bridge, spring-boot-dev bundle), stale comments in logback-spring.xml and SolrNativeHints, unrelated Jib comments, and the AGENTS.md notes rewritten as migration notes with the actual SDK version. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CiUHyyXLTo9ATdgg8eRFZJ Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Upgrades the server to Spring Boot 4.1.1 and Spring AI 2.0.1. Both are GA on Maven Central; no milestone repositories are required. 46 files, +910 / −194 against
main.Versions
mainmcp-bom(Spring AI 2.0.1 resolves 2.0.0; 2.0.1 fixes stateless-server error handling, which is our HTTP profile, and adds bounded reads on the HTTP and STDIO transports — drop the pin once Spring AI ships it)mcp-server-securitymcp-core0.x)mcp-core2.0) — catalog change only,McpServerOAuth2ConfigurerAPI unchangedtestcontainers-junit-jupiter,testcontainers-solr,testcontainers-grafana)CyclonedxPluginActionrecognises 3.x)Code changes
Jackson 3.
tools.jackson.databindreplacescom.fasterxml.jackson.databindinSolrConfig,JsonResponseParser,JsonDocumentCreator,JsonUtils,CollectionService,SchemaService.JacksonExceptionreplacesIOException/JsonProcessingExceptionin catch clauses;JsonNode.properties()/asString()replacefields()/asText(). Annotations stay incom.fasterxml.jackson.annotation. The MCP SDK side usesmcp-json-jackson3, so the test clients build their transports withio.modelcontextprotocol.json.jackson3.JacksonMcpJsonMapperoverJsonMapper.builder().build().MCP annotations. Package moved from
org.springaicommunity.mcp.annotationtoorg.springframework.ai.mcp.annotation(now part of Spring AI core) in all four service classes. The MCP SDK 2.0 also validates request arguments at the boundary, soCollectionServiceTest's null-completion case now asserts the SDK rejects a nullCompleteArgumentinstead of exercising the service.Modular starters.
spring-boot-starter-web→-webmvc+-json;-aop→-aspectj(what@Observedneeds on Boot 4); modular test starters (-webmvc-test,-actuator-test,-opentelemetry-test).spring-ai-autoconfigure-mcp-server-commonand-client-commonare declared explicitly: Spring AI 2.0.0-M7 marked them optional in the starter POMs, but the webmvc autoconfig still references types from them.spring-boot-starter-mongodbis excluded from Spring AI's docker-compose module, which otherwise tries to build a Mongo client onbootRun.Observability.
spring-boot-starter-opentelemetry(traces, metrics, OTLP log export) replaces the manual OTel instrumentation BOM 2.11.0 +micrometer-tracing-bridge-otel+ Prometheus registry. The OTel logback appender (2.21.0-alpha) is installed programmatically by a newInstallOpenTelemetryAppenderbean (@Profile("http")), andapplication-http.propertiesswitches tomanagement.opentelemetry.*/management.otlp.*endpoints. TworesolutionStrategyrules keep the OTel graph coherent:opentelemetry-api-incubatoraligned to1.62.0-alpha(the appender pins1.55.0-alpha, which lacksDeclarativeConfigProperties.get(String)and fails at context startup), andopentelemetry-protoheld at1.3.2-alpha(the1.8.0-alphaline needs protobuf 4).@Observedspan names change fromsearch-service#searchtoSearchService#searchunder the AspectJ starter;DistributedTracingTestis updated for the new names and disables export with the currentmanagement.tracing.export.otlp.enabledproperty. With the Prometheus registry gone,/actuator/prometheusis no longer exposed or documented; metrics leave via OTLP only.Profiles.
application-stdio.propertiesexcludes the Boot 4.1 security auto-configuration classes (org.springframework.boot.security.autoconfigure.SecurityAutoConfigurationand...actuate.web.servlet.ManagementWebSecurityAutoConfiguration); the Boot 3 names would be silently ignored.spring.application.namestayssolr-mcpin every profile.Logging under Spring Framework 7.
application-stdio.propertiesno longer setslogging.pattern.console=. Boot copies that value into the JVM-wideCONSOLE_LOG_PATTERNsystem property (first writer wins) and logback rejects an empty pattern, so it silenced nothing — but Framework 7 pauses a test's context on context switch and makes the next context re-initialise logback, so a stdio-profile test running first broke every later http-profile context in the same JVM. STDIO stays silent becauselogback-spring.xmldeclares no appenders for it (unchanged), andLoggingConfigurationTestgains a guard against the empty pattern coming back.GraalVM native.
MemberCategory.DECLARED_FIELDS(removal-marked) →ACCESS_DECLARED_FIELDS. New hints for the SolrJ 10 API model types (SolrJerseyResponse,ErrorInfo,CoreStatusResponseand nested), the relocatedorg.springframework.ai.mcp.annotation.context.DefaultMetaProvider, and Solr'sEnvToSyspropMappings.properties/DeprecatedSystemPropertyMappings.propertiesresources. Error Prone / NullAway are disabled for thecompileAotJava/compileAotTestJavatasks, whose generated sources cannot be edited.SBOM. With cyclonedx 3.x the work splits into
cyclonedxDirectBom(resolves, ownsincludeConfigs) andcyclonedxBom(aggregates). The direct task is scoped toproductionRuntimeClasspath; left at defaults it adds ~100 test-only components that scanners would read as shipped. The license-notice plugin reads the SBOM from its new location,build/reports/cyclonedx/application.cdx.json. The old 2.4.1 pin turned out to ship no SBOM in the bootJar at all, because Boot's action bails on an unrecognised plugin version (#186).compose.yaml.
grafana/otel-lgtmpinned to0.30.0; Grafana and the OTLP receivers bind to loopback by default (GRAFANA_BIND/OTLP_BINDto override), anonymous access is Viewer rather than Admin (GF_ANON_ROLE=Adminto opt in); datasource list corrected to Prometheus, not Mimir. The service keeps itsorg.springframework.boot.ignorelabel, so Boot's compose support leaves it alone as onmain.Docs.
AGENTS.md(framework versions, SBOM section, logging note, Spring Boot 4 Notes as migration notes),docs/observability.md(new endpoint scheme, per-signal variables, migration callout), newdev-docs/Observability.md(LGTM stack, architecture, production guidance),dev-docs/graalvm-native-image.md(OTel alignment replaces the old "BOM bump blocked" note),docs/security/stdio.md,README.md.Verification
Branch merges
main@b4ffe18(including #193)../gradlew buildon Java 25, head7a0a094./gradlew nativeTest -Pnativeon GraalVM CE 25.0.2, headd6e973d(the config/docs cleanup since then touches no test class)main: 227 / 0 / 142 — the delta is added tests, not skips)./gradlew dockerIntegrationTest(JVM or native)Coverage caveat, unchanged from
main: no automated test exercisesMcpServerOAuth2Configureron any version. It is wired only whenOAUTH2_ISSUER_URIis set, andDockerImageHttpIntegrationTestruns without an issuer. #187 adds the first security-enabled context tests and applies here unchanged.References
🤖 Generated with Claude Code
https://claude.ai/code/session_01CiUHyyXLTo9ATdgg8eRFZJ