feat: Spring Boot 4 + Spring AI 2.0 upgrade - #23
Conversation
|
Currently using Milestone version of Spring AI 2.0. Will make it ready for review once Spring AI 2.0 is GA. |
|
Thanks for the update on this one... We will merge when we get an official Spring AI 2.0 release! |
* 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>
|
Re-triggering CI now that ci.yml has landed on main (#117). |
|
Looks like you have some conflicts.. I think you need to update from main to pick up the new ci stuff? |
6bc312d to
6427366
Compare
9bbfae5 to
857fa32
Compare
|
Let's get 1.0 out. |
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>
|
Rebased onto I have not force-pushed this PR's branch. Its head is Four conflicts, all from the Spring AI 2.0 relocation meeting upstream's jspecify work:
Two things the rebase broke semantically — both merged cleanly and failed only under javac/JUnit, so they are worth naming:
Still parked behind the 1.0 release per the earlier discussion; this is just to keep it merge-ready. |
|
Updated: The previous |
…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>
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>
|
Pushed 6364bd3 addressing review findings. Versions bumped4.1.0 → 4.1.1 and 2.0.0 → 2.0.1, both checked against The substantive finding: observability docs would have broken usersThis PR moves OTLP export from gRPC on 4317 to HTTP on 4318, with a separate full URL per
export OTEL_TRACES_URL=https://otel-collector.example.com:4317Following that after this merged would not error. Traces would just stop arriving, which is Fixed:
Also worth flagging as breaking in the release notes, not just the docs: anyone with Smaller items
Verification
Merge-order noteThis PR and #175 both rewrite the same line in |
Upgrades the Solr MCP server to Spring Boot 4.1.1 and Spring AI 2.0.1.
This PR is scoped to the Spring Boot 4 / Spring AI 2.0 upgrade only, rebased onto current
main(a84033b). Changes that had accumulated on thesb4branch but are unrelated to the upgrade (CI Solr-version-matrix tweaks, Solr 10/admin/metricstest skips, the docker-image-tag-suffix helper superseded by #139, and cosmetic refactors) have been intentionally excluded.Both are current GA releases on Maven Central (verified against
maven-metadata.xml), so no milestone repositories are required.Highlights
tools.jackson.databindreplacescom.fasterxml.jackson.databind(annotations stay incom.fasterxml.jackson.annotation); the MCP SDK usesmcp-json-jackson3.JacksonExceptionreplacesIOException/JsonProcessingException;JsonNode.properties()/asString()replacefields()/asText().org.springaicommunity.mcp.annotation→org.springframework.ai.mcp.annotation(now part of Spring AI core).spring-boot-starter-web→-webmvc+-json; explicitspring-ai-autoconfigure-mcp-server-common(no longer transitive after Spring AI 2.0.0-M7); modular test starters.spring-boot-starter-opentelemetry(SB4 idiomatic) for traces, metrics, and OTLP log export, replacing the manual OTel BOM +micrometer-tracing-bridge-otelapproach;spring-boot-starter-aspectjfor@Observed; OTel logback appender installed programmatically viaInstallOpenTelemetryAppender;management.opentelemetry.*OTLP properties.DefaultMetaProvider+ Solr API model reflection hints, per-profile AOT docs; dependency-resolution alignment foropentelemetry-protoandopentelemetry-api-incubatoragainst the SB 4.1.0 BOM.mcp-server-security0.0.6 → 0.1.14 — see below.mcp-server-securityupgrade0.0.6 is built against the Spring Boot 3 line: its POM declares
spring-core6.2.9 andspring-security-{web,config,core,oauth2-*}6.5.2, and it predatesmcp-core2.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 was never compiled for.0.1.14 is the current release of the same library on the Boot 4 line:
spring-core7.0.8,spring-security-*7.1.0,mcp-core2.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; theMcpServerOAuth2ConfigurerAPI used inHttpSecurityConfigurationis unchanged.Coverage caveat, flagged for reviewers: no automated test in this repo exercises
McpServerOAuth2Configureron any version. It is wired only whenOAUTH2_ISSUER_URIis set (HttpSecurityConfiguration#secured), andDockerImageHttpIntegrationTestdeliberately runs without an issuer since it has no IdP. That gap is pre-existing and is not introduced by this PR, but it does mean the OAuth2 wiring path is covered by neither the old nor the new version here. Worth closing separately.Rebase notes
Conflicts against current
mainwere resolved to keep both sides rather than picking a winner:build.gradle.ktscompileTestJavadisable (#179) and this branch'scompileAot{,Test}Javadisable. Different tasks.SolrConfig.javajava.net.URI(new URL validation) plustools.jackson.databind.ObjectMapper; dropped the Jackson 2 import.SearchService.java,CollectionService.javaorg.jspecify.annotations.Nullableplus the relocatedorg.springframework.ai.mcp.annotation.*.SolrNativeHints.javaDefaultMetaProvidercoordinate registered; the legacyspringaicommunityname kept as a defensive no-op.Two breaks surfaced only on build/test, not as textual conflicts:
mainstill usesimport static org.junit.jupiter.api.Assertions.*, which this branch had expanded to explicit imports. Git merged the explicit list, so assertionsmainadded since the fork point (feat(search): append remediation hints to common Solr query errors #166assertThrows, feat(indexing): report indexed field names in tool responses #165assertFalse) had no import. Added the two rather than reintroducing the wildcard.SolrNativeHintsTestfailure. The test added onmainvia fix(native): register DefaultMetaProvider reflection hint to unbreak nativeTest #172 asserts a hint fororg.springaicommunity.mcp.context.DefaultMetaProvider. Spring AI 2.x relocated that class into Spring AI core, andregisterTypeIfPresentsilently no-ops for a class absent from the classpath — so the legacy coordinate registers nothing here and the assertion failed. The test now pins the coordinate Spring AI 2.x actually ships.Upstream work landed since the fork point was verified to survive the rebase: all new
package-info.javafiles, #166's search remediation hints, and #165's indexed-field reporting. The service-file diffs versusmainare now import migrations only.Verification
All on JDK 25 (GraalVM 25.0.2 for the native path):
./gradlew build./gradlew nativeTest -Pnative./gradlew dockerIntegrationTestReferences
🤖 Generated with Claude Code