Spring Boot 4 & Spring Framework 7¶
At a glance
Priority: P0 · Complexity: ⅖ · Est. time: 3 h · Phase: 1 · Prereqs: Modern Java, prior Spring Boot ⅔ experience You're done when: you can scaffold a Boot 4 service on JDK 25 with Spring AI 2.0, explain the five Boot 4 changes that bite during migration (modular starters, Jackson 3, Jakarta EE 11, JSpecify, removed/renamed properties), and add API versioning + declarative retry without extra libraries.
Why it matters¶
Spring AI 2.0 (GA 2026-06-12) requires Spring Boot 4.0/4.1 and Spring Framework 7 — there is no Spring AI 2.0 on Boot 3. So the Boot 4 jump is the gate to everything else in this track. Boot 4.0 went GA in November 2025 (4.1 is also out as of September 2026). For an engineer returning from Boot ⅔ days, the platform now gives you, out of the box, things you used to bolt on: API versioning, retry and concurrency limiting, declarative HTTP clients, null-safety annotations, and first-class OpenTelemetry.
Architect angle: most orgs will run Boot 3 and Boot 4 side by side for a year. Knowing the migration seams (Jackson 3, modular auto-config, Jakarta EE 11) is what lets you plan it without stalling AI delivery.
Core concepts¶
What's new, grouped by why you care¶
| Area | Boot 4 / Framework 7 change | Why it matters for AI services |
|---|---|---|
| Baseline | Java 17 minimum (JDK 25 recommended), Jakarta EE 11 (Servlet 6.1), Kotlin 2.2 | Run on the LTS; virtual threads mature |
| Modularisation | Boot's auto-configuration split into many focused modules; technology-specific starters (e.g., spring-boot-starter-webmvc); a "classic" starter path for gradual migration |
Smaller classpaths, faster startup, fewer surprise auto-configs |
| JSON | Jackson 3 (tools.jackson.* packages, JsonMapper); Jackson 2 supported in deprecated form |
Spring AI 2.0 uses Jackson 3; custom serializers need porting |
| Null-safety | JSpecify annotations across Spring portfolio (and Spring AI 2.0) | Kotlin/IDE/NullAway checking of @Nullable returns like entity(...) |
| Resilience | @Retryable, @ConcurrencyLimit, @EnableResilientMethods, RetryTemplate in Spring Framework core |
Retry LLM/tool calls and bulkhead virtual-thread fan-out without Resilience4j for simple cases |
| API versioning | First-class version attribute on request mappings; header/query/path/media-type resolution; spring.mvc.apiversion.* properties |
Version tool/agent APIs consumed by other teams |
| HTTP clients | HTTP service client auto-configuration (@HttpExchange interfaces, @ImportHttpServices) |
Typed clients for internal tools the agent calls |
| Observability | New spring-boot-starter-opentelemetry; Micrometer 1.16-era observations |
Spring AI emits Micrometer observations; export OTLP without agents |
| Testing | RestTestClient; Testcontainers 2.0 |
Integration tests for RAG against real pgvector |
| Virtual threads | spring.threads.virtual.enabled=true now also covers auto-configured JDK HttpClient-based clients |
Blocking LLM calls scale |
Minimal Boot 4 + Spring AI 2.0 build¶
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.1</version> <!-- check start.spring.io for the latest 4.x patch -->
</parent>
<properties>
<java.version>25</java.version>
<spring-ai.version>2.0.1</spring-ai.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId> <!-- or -anthropic, -ollama -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
plugins {
java
id("org.springframework.boot") version "4.1.1"
}
java { toolchain { languageVersion = JavaLanguageVersion.of(25) } }
dependencies {
implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
implementation(platform("org.springframework.ai:spring-ai-bom:2.0.1"))
implementation("org.springframework.boot:spring-boot-starter-webmvc")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.ai:spring-ai-starter-model-openai")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
As of September 2026 the Spring AI BOM's latest GA is 2.0.1 (a patch release with CVE fixes over 2.0.0); Spring AI's own build targets Boot 4.1.x. Always take versions from start.spring.io rather than blog posts.
API versioning without a library¶
@RestController
@RequestMapping("/api/shipments")
class ShipmentController {
@GetMapping(path = "/{id}/summary", version = "1.0")
SummaryV1 summaryV1(@PathVariable String id) { ... }
@GetMapping(path = "/{id}/summary", version = "2.0+") // 2.0 and later
SummaryV2 summaryV2(@PathVariable String id) { ... } // LLM-generated summary + citations
}
@Configuration
class ApiVersionConfig implements WebMvcConfigurer {
@Override
public void configureApiVersioning(ApiVersionConfigurer configurer) {
configurer.useRequestHeader("API-Version");
}
}
Unsupported versions → 400 (InvalidApiVersionException); deprecation can be advertised with Deprecation/Sunset headers via ApiVersionDeprecationHandler. Boot exposes equivalent spring.mvc.apiversion.* properties. Links to API contracts & versioning.
Declarative resilience in core Spring¶
@Configuration
@EnableResilientMethods
class ResilienceConfig {}
@Service
class ForecastTool {
@Retryable(includes = HttpServerErrorException.class, maxRetries = 3,
delay = 200, multiplier = 2, jitter = 50, maxDelay = 2000)
@ConcurrencyLimit(16) // bulkhead for virtual-thread callers
public Forecast forPort(String unlocode) { return client.forecast(unlocode); }
}
Nuance: retry is for idempotent operations. Retrying an LLM call is usually safe (costly, not harmful); retrying a tool that books a container is not — add idempotency keys. Don't retry 429s blindly at this layer if a gateway already does. For circuit breakers and rate limiters you still want Resilience4j (or a gateway).
Jackson 3 migration — what actually breaks¶
- Packages move:
com.fasterxml.jackson.databind.ObjectMapper→tools.jackson.databind.json.JsonMapper(immutable, builder-configured). Annotations stay incom.fasterxml.jackson.annotation. - Checked
JsonProcessingException→ uncheckedJacksonException. - Some defaults changed (e.g., date handling and property ordering); snapshot-test your public JSON.
- Custom
Modules and serializers need recompiling against Jackson 3.
JSpecify null-safety¶
Boot 4, Framework 7 and Spring AI 2.0 annotate APIs with JSpecify @Nullable/@NullMarked. Practical effect: chatClient.prompt()...call().entity(Foo.class) is declared @Nullable — your IDE (and NullAway, if you adopt it) will make you handle the empty-response case. Adopt @NullMarked in package-info.java for new AI modules; it's the cheapest way to catch "model returned nothing" NPEs.
Migration path from Boot 3.x¶
flowchart LR
A[Boot 3.5 on JDK 17/21] --> B[Fix deprecations<br/>on 3.5]
B --> C[JDK 25 in CI]
C --> D[Boot 4 + classic starters<br/>Jackson 2 compat if needed]
D --> E[Modular starters,<br/>Jackson 3, JSpecify]
E --> F[Spring AI 2.0]
Upgrade to the last 3.x first and clear deprecation warnings (Boot removes what 3.x deprecated), then jump. OpenRewrite has Boot migration recipes; the official Boot 4.0 release notes and the Spring AI upgrade notes are the checklist. Spring AI 2.0 also removed the .options segment from many property keys (e.g., spring.ai.openai.chat.model instead of spring.ai.openai.chat.options.model) — grep your configs.
Python/FastAPI mapping¶
| Concern | Spring Boot 4 | FastAPI world |
|---|---|---|
| DI | Constructor injection, beans | Depends() |
| Config | application.yml, @ConfigurationProperties records |
pydantic-settings |
| Validation | Jakarta Validation on records | Pydantic models |
| API versioning | Built-in version mapping |
Router prefixes / custom |
| Retry | @Retryable (core) |
tenacity |
| Health/metrics | Actuator + Micrometer | custom / prometheus-fastapi-instrumentator |
Resources¶
| Resource | Type | Why this one | Level | Cost |
|---|---|---|---|---|
| Spring Boot 4.0 release notes | docs | The migration checklist: modules, Jackson 3, removed properties | intermediate | free |
| Spring Framework 7.0 release notes | docs | Versioning, resilience, JSpecify, HTTP service clients | intermediate | free |
| Spring Boot 4.0.0 announcement | article | Official summary of the headline features | intermediate | free |
| Spring Framework: resilience features | docs | Exact @Retryable/@ConcurrencyLimit semantics |
intermediate | free |
| Spring MVC API versioning | docs | Resolution strategies, deprecation headers | intermediate | free |
| Dan Vega |
article/video | Short, practical Boot 4 and Spring AI walkthroughs; best "show me the code" source | intermediate | free |
| Josh Long — Spring Tips |
video | Live-coded tours of every new Boot/Framework feature, including AOT and virtual threads | intermediate | free |
| JSpecify | docs | What the null-safety annotations mean and how tools check them | intermediate | free |
Hands-on lab¶
Goal (90 min): create the Java service skeleton for the capstone that later labs extend (RAG, MCP tools).
- On start.spring.io pick Boot 4.1.x, Java 25, Maven; add Spring Web (MVC), Actuator, Validation, OpenAI (or Ollama for local), PostgreSQL Driver, Docker Compose support, Testcontainers.
application.yml:spring.threads.virtual.enabled: true, model config (spring.ai.openai.api-key: ${OPENAI_API_KEY},spring.ai.openai.chat.model: <model>— orspring.ai.ollama.chat.model).- Add a versioned
/api/shipments/{id}/summaryendpoint (v1 static, v2 viaChatClient, see Spring AI fundamentals). - Add a
ForecastToolwith@Retryable+@ConcurrencyLimit; stub the HTTP client to fail twice then succeed; assert 3 calls in a test usingRestTestClient/MockMvc. - Add
@NullMarkedto your root package and fix the warnings the IDE raises. - Run
./mvnw spring-boot:runand hitcurl -H 'API-Version: 2.0' localhost:8080/api/shipments/MSKU123/summary.
Expected output: v1 and v2 responses differ by version header; a 400 for API-Version: 9.0; the retry test passes; Actuator /actuator/health UP.
Questions¶
L1 — Recall¶
Q1. Which Spring Boot and Framework versions does Spring AI 2.0 require?
Answer
Spring Boot 4.0 or 4.1 and Spring Framework 7.0 (plus Jackson 3). Spring AI 1.x targets Boot 3.x. There's no supported combination of Spring AI 2.0 with Boot 3.
Q2. Name four capabilities Boot 4 / Framework 7 give you that previously needed extra libraries.
Answer
API versioning (version attribute on mappings), retry and concurrency limiting (@Retryable, @ConcurrencyLimit, RetryTemplate in core), HTTP service client auto-configuration for @HttpExchange interfaces, JSpecify null-safety annotations, a dedicated OpenTelemetry starter, and RestTestClient for tests.
Q3. What are the main code-level changes when moving from Jackson 2 to Jackson 3?
Answer
Core/databind packages move to tools.jackson.*; JsonMapper (immutable, builder-configured) is the primary mapper; exceptions become unchecked (JacksonException); annotations remain in com.fasterxml.jackson.annotation; some defaults change, so JSON output should be snapshot-tested.
L2 — Apply¶
Q4. Add a v2 endpoint that returns an LLM summary while v1 keeps the old contract, versioned by header.
Answer
Two methods with the same path and version = "1.0" / version = "2.0+", plus configureApiVersioning with configurer.useRequestHeader("API-Version") (or the spring.mvc.apiversion.* properties). Mark v1 deprecated with an ApiVersionDeprecationHandler that emits Deprecation/Sunset headers, and track v1 traffic before removing it.
Q5. A tool calling an internal pricing API fails with intermittent 503s. Add retries safely.
Answer
@EnableResilientMethods on a config class, then @Retryable(includes = HttpServerErrorException.ServiceUnavailable.class, maxRetries = 3, delay = 200, multiplier = 2, jitter = 50, maxDelay = 2000) on the method. Confirm the call is idempotent (GET) — for writes add an idempotency key. Keep retries in one layer only (not also in the HTTP client and the agent). Surface exhaustion to the model as a tool error message so it can respond gracefully rather than retrying itself.
Q6. After upgrading, application.yml has spring.ai.openai.chat.options.model and the model is ignored. Why?
Answer
Spring AI 2.0 removed the artificial .options segment from many property keys; the key is now spring.ai.openai.chat.model. Check the Spring AI upgrade notes, and enable Boot's spring-boot-properties-migrator temporarily to log renamed/removed keys at startup.
L3 — Design & trade-offs¶
Q7. Spring's built-in @Retryable/@ConcurrencyLimit vs Resilience4j — when is each right?
Answer
Built-ins: simple retry with backoff/jitter and a concurrency bulkhead, zero extra dependencies, good for most internal tool calls. Resilience4j: circuit breakers, rate limiters, time limiters, sliding-window metrics, per-instance config/registries and richer observability. For LLM calls, the hard problems (rate limits, fallbacks across providers, budget) are often better centralised in an LLM gateway than per-service annotations. Pick built-ins by default; add Resilience4j when you need breakers or rate limiting in-process.
Q8. Big-bang Boot 3 → 4 migration of a 30-module monolith vs module-by-module — decide.
Answer
A single deployable can't run Boot 3 and 4 simultaneously, so "module-by-module" means preparing modules on 3.5 (clear deprecations, isolate Jackson customisations behind one config, move to Jakarta EE 11-compatible libraries, JDK 25 in CI) and then a single flip, possibly using the classic starters and Jackson 2 compatibility to reduce the blast radius. Alternatively carve new AI features into a separate Boot 4 service (strangler) so AI work isn't blocked by the monolith migration. Most orgs do both.
Q9. Should new AI services default to MVC + virtual threads or WebFlux on Boot 4?
Answer
MVC + virtual threads by default: simpler, blocking JDBC, easier debugging, and Spring AI streaming (Flux<String>) can still be returned as SSE from MVC controllers. WebFlux when the service is a streaming gateway with huge numbers of long-lived connections and backpressure matters end to end, or when the team is already reactive. See virtual threads.
L4 — Staff-level ambiguity¶
Q10. Three product teams want Spring AI now; the shared platform libraries are still on Boot 3. How do you unblock them without forking the platform?
Answer
Offer a time-boxed "AI paved road": a Boot 4 + Spring AI 2.0 service template (separate deployable) that depends only on platform libraries that are Boot-4-compatible, with gaps filled by thin adapters (auth, logging, config). Platform team commits to a Boot 4 compatibility release by a date; product teams agree to adopt it and delete adapters. Governance: ADR, an owner for the template, and a sunset date for the adapters. Avoid each team hand-rolling its own Boot 4 stack, which creates three future migrations.
Q11. Architecture review: a team proposes Python (FastAPI + Pydantic AI) for a new AI feature in a Java-only org. Your position?
Answer
Don't decide on language preference; decide on constraints: where the data and transactions live (Java domain services), team skills and on-call ownership, library maturity for the specific AI need (eval/optimisation tooling like DSPy is Python-first; Spring AI is strong for RAG/tools/MCP inside existing Java estates), and operational standards (observability, security, deployment). A common resolution: Java owns domain tools and RAG services exposed via MCP/HTTP; Python owns experimentation-heavy orchestration if needed. See polyglot AI architecture. Capture it in an ADR with a reversal trigger.
Real-world use cases¶
- Versioned shipment-summary API consumed by web, mobile and partner integrations — v2 adds LLM-generated narrative with citations while v1 stays stable.
- Tool microservices for an agent (pricing, schedules, customs rules) with core
@Retryableand bulkheads, exposed via MCP (see tools & MCP). - Strangler migration: new Boot 4 AI service beside a Boot 3 monolith, sharing the database via read-only views or events.
Pitfalls & anti-patterns¶
- Jumping from Boot 3.0/3.1 straight to 4 without passing through 3.5 deprecation cleanup.
- Leaving Jackson 2
ObjectMapperbeans around that silently diverge from Spring AI's Jackson 3 mapper. - Retrying non-idempotent tool calls.
- Treating
entity(...)results as non-null. - Pinning versions from blog posts; always verify against start.spring.io and the BOM.
Checklist¶
- I can list Boot 4's key changes and why each matters for AI services
- I scaffolded the Boot 4 + Spring AI 2.0 skeleton on JDK 25
- I implemented header-based API versioning and a retry-tested tool
- I know the Jackson 3 and Spring AI property-key migration gotchas
- I answered all L3 questions out loud in < 3 min each