Skip to content

feat: upgrade to Spring Boot 4.1.1 and Spring AI 2.0.1 - #23

Open
adityamparikh wants to merge 15 commits into
apache:mainfrom
adityamparikh:sb4
Open

adityamparikh wants to merge 15 commits into
apache:mainfrom
adityamparikh:sb4

Conversation

@adityamparikh

@adityamparikh adityamparikh commented Dec 13, 2025

Copy link
Copy Markdown
Contributor

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.

Breaking configuration change (HTTP profile only): OTLP export moves from gRPC on port 4317 to OTLP/HTTP on 4318, with one complete URL per signal. OTEL_TRACES_URL changes meaning — it was a base endpoint (http://collector:4317) and is now the full traces path (http://collector:4318/v1/traces). A value carried over unchanged does not error; traces simply stop arriving. OTEL_METRICS_URL and OTEL_LOGS_URL are new. See docs/observability.md.

Versions

Component main This PR
Spring Boot 3.5.14 4.1.1
Spring AI 1.1.7 2.0.1
MCP Java SDK 0.18.x (transitive) 2.0.1, pinned via mcp-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-security 0.0.6 (built against Security 6.5 / mcp-core 0.x) 0.1.14 (Security 7.1 / mcp-core 2.0) — catalog change only, McpServerOAuth2Configurer API unchanged
Testcontainers 1.21.3 2.0.2 (module renames: testcontainers-junit-jupiter, testcontainers-solr, testcontainers-grafana)
CycloneDX Gradle plugin 2.4.1 (pinned) 3.4.1 (Boot 4.1.1's CyclonedxPluginAction recognises 3.x)
Apache RAT plugin 0.8.1 0.8.2
JSpecify explicit dependency dropped — built into Spring Boot 4

Code changes

Jackson 3. tools.jackson.databind replaces com.fasterxml.jackson.databind in SolrConfig, JsonResponseParser, JsonDocumentCreator, JsonUtils, CollectionService, SchemaService. JacksonException replaces IOException / JsonProcessingException in catch clauses; JsonNode.properties() / asString() replace fields() / asText(). Annotations stay in com.fasterxml.jackson.annotation. The MCP SDK side uses mcp-json-jackson3, so the test clients build their transports with io.modelcontextprotocol.json.jackson3.JacksonMcpJsonMapper over JsonMapper.builder().build().

MCP annotations. Package moved from org.springaicommunity.mcp.annotation to org.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, so CollectionServiceTest's null-completion case now asserts the SDK rejects a null CompleteArgument instead of exercising the service.

Modular starters. spring-boot-starter-web-webmvc + -json; -aop-aspectj (what @Observed needs on Boot 4); modular test starters (-webmvc-test, -actuator-test, -opentelemetry-test). spring-ai-autoconfigure-mcp-server-common and -client-common are 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-mongodb is excluded from Spring AI's docker-compose module, which otherwise tries to build a Mongo client on bootRun.

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 new InstallOpenTelemetryAppender bean (@Profile("http")), and application-http.properties switches to management.opentelemetry.* / management.otlp.* endpoints. Two resolutionStrategy rules keep the OTel graph coherent: opentelemetry-api-incubator aligned to 1.62.0-alpha (the appender pins 1.55.0-alpha, which lacks DeclarativeConfigProperties.get(String) and fails at context startup), and opentelemetry-proto held at 1.3.2-alpha (the 1.8.0-alpha line needs protobuf 4). @Observed span names change from search-service#search to SearchService#search under the AspectJ starter; DistributedTracingTest is updated for the new names and disables export with the current management.tracing.export.otlp.enabled property. With the Prometheus registry gone, /actuator/prometheus is no longer exposed or documented; metrics leave via OTLP only.

Profiles. application-stdio.properties excludes the Boot 4.1 security auto-configuration classes (org.springframework.boot.security.autoconfigure.SecurityAutoConfiguration and ...actuate.web.servlet.ManagementWebSecurityAutoConfiguration); the Boot 3 names would be silently ignored. spring.application.name stays solr-mcp in every profile.

Logging under Spring Framework 7. application-stdio.properties no longer sets logging.pattern.console=. Boot copies that value into the JVM-wide CONSOLE_LOG_PATTERN system 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 because logback-spring.xml declares no appenders for it (unchanged), and LoggingConfigurationTest gains 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, CoreStatusResponse and nested), the relocated org.springframework.ai.mcp.annotation.context.DefaultMetaProvider, and Solr's EnvToSyspropMappings.properties / DeprecatedSystemPropertyMappings.properties resources. Error Prone / NullAway are disabled for the compileAotJava / compileAotTestJava tasks, whose generated sources cannot be edited.

SBOM. With cyclonedx 3.x the work splits into cyclonedxDirectBom (resolves, owns includeConfigs) and cyclonedxBom (aggregates). The direct task is scoped to productionRuntimeClasspath; 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-lgtm pinned to 0.30.0; Grafana and the OTLP receivers bind to loopback by default (GRAFANA_BIND / OTLP_BIND to override), anonymous access is Viewer rather than Admin (GF_ANON_ROLE=Admin to opt in); datasource list corrected to Prometheus, not Mimir. The service keeps its org.springframework.boot.ignore label, so Boot's compose support leaves it alone as on main.

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), new dev-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).

Command Result
./gradlew build on Java 25, head 7a0a094 404 tests, 0 failures, 7 skipped
./gradlew nativeTest -Pnative on GraalVM CE 25.0.2, head d6e973d (the config/docs cleanup since then touches no test class) 257 successful, 0 failed, 144 skipped (main: 227 / 0 / 142 — the delta is added tests, not skips)
./gradlew dockerIntegrationTest (JVM or native) not run

Coverage caveat, unchanged from main: no automated test exercises McpServerOAuth2Configurer on any version. It is wired only when OAUTH2_ISSUER_URI is set, and DockerImageHttpIntegrationTest runs 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

@adityamparikh
adityamparikh marked this pull request as draft December 16, 2025 22:10
@epugh

epugh commented Jan 6, 2026

Copy link
Copy Markdown
Contributor

Thanks for the update on this one... We will merge when we get an official Spring AI 2.0 release!

@epugh epugh changed the title feat: Spring Boot 4 upgrade feat: Spring Boot 4 + Spring AI 2.0 upgrade Jan 6, 2026
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>
@adityamparikh adityamparikh reopened this May 2, 2026
@epugh

epugh commented May 2, 2026

Copy link
Copy Markdown
Contributor

Looks like you have some conflicts.. I think you need to update from main to pick up the new ci stuff?

@adityamparikh

adityamparikh commented Jul 30, 2026

Copy link
Copy Markdown
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 main; see the Verification section there for the latest build and native results.

adityamparikh and others added 5 commits August 19, 2026 07:35
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>
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>
adityamparikh and others added 7 commits September 7, 2026 22:10
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>
@adityamparikh

Copy link
Copy Markdown
Contributor Author

Verified the current head d6e973d (which merges main @ b4ffe18, including #193's logging rework) in a clean worktree:

Command Result
./gradlew build (JDK 25) 404 tests, 0 failures, 7 skipped
./gradlew nativeTest -Pnative (GraalVM CE 25.0.2) 257 successful, 0 failed, 144 skipped, BUILD SUCCESSFUL in 8m 41s

For comparison main measures 227 / 0 / 142 natively, so the delta is added tests, not skips. No conflicts with main; GitHub reports the PR mergeable.

@adityamparikh adityamparikh changed the title feat: Spring Boot 4 + Spring AI 2.0 upgrade feat: upgrade to Spring Boot 4.1.1 and Spring AI 2.0.1 Sep 12, 2026
- 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants