A Spring Boot upgrade succeeds when existing clients, transactions and deployment procedures still work. A green compilation is useful evidence, but it is only one part of that contract.
Spring Boot 4.1 is part of the June 2026 Spring release train, as recorded in the official release roundup. This guide was reviewed on 29 September 2026. It proposes an upgrade rehearsal for a hypothetical interview-booking backend using Spring Boot, PostgreSQL, Redis and Kafka, matching the technical areas of my portfolio without claiming that the migration has been performed on an existing project.
Record the runtime you actually have
Before editing the parent version, capture the Java runtime, build wrapper, application dependencies, container base image and deployment configuration. Record which services call the backend and which event consumers read its messages. The dependency tree is more useful than a short list of direct dependencies because transitive libraries also participate in the runtime.
Take a baseline from representative requests: authentication, available-slot search, booking creation, cancellation and duplicate submission. Save expected status codes and response bodies alongside database effects. For messaging, capture sanitized example events and the expected consumer outcome. These are contracts to preserve, not a performance competition between two empty applications.
Use the official system requirements for the exact maintenance release selected for implementation. Keep the version in the build reproducible. This article discusses the 4.1 release line rather than declaring a particular patch release permanently current. Recheck dependency and security updates at the time of the upgrade.
Separate the major-version work from 4.1 changes
For applications starting on Boot 3, the Boot 4.0 migration guide recommends first reaching the latest 3.5 maintenance release, reviewing deprecations and checking dependencies outside Boot's management. Work through that major-version boundary before evaluating the additional 4.1 changes. Spring Cloud compatibility deserves explicit review when the application uses a gateway or discovery components.
Boot 4 introduced a modularized codebase and a new Spring Framework generation. That affects dependency selection and package locations, including test support. The Boot 4.0 release announcement is a useful map of those changes. Do not solve every missing import by adding a large collection of starters until the test happens to compile.
Make the work reviewable in stages: baseline tests, dependency alignment, compilation fixes, behavior fixes and optional feature adoption. The stages can live in one migration branch. Their purpose is to keep each failure attributable to a specific change, rather than simultaneously changing the database, JVM, framework and deployment platform.
Treat JSON as a public interface
Boot 4 prefers Jackson 3, and the migration guide documents package and configuration changes. In particular, the Jackson annotations package is an exception to the broader package-name migration. Avoid a blind text replacement across all Jackson imports. Identify custom serializers, mapper beans and message converters, then adapt each against the documented extension point.
For a booking API, compare a timestamp with an offset, a missing optional field, an explicitly null field, a decimal value and an unknown enum value. Also compare validation failures. A client can depend on an error object's field names just as strongly as it depends on a successful response. Capture the output through the HTTP layer so the actual configured mapper participates.
// Illustrative contract fixture, not a complete application test.
String expectedBooking = """
{
"status": "CONFIRMED",
"startsAt": "2026-10-12T10:00:00Z",
"durationMinutes": 30
}
""";
// Compare the endpoint's JSON structurally against the agreed contract.
// Also assert the stored booking and the emitted event separately.
Structural JSON comparison should ignore irrelevant object-key ordering while still enforcing the fields the client requires. Choose deliberately whether extra fields are accepted. Do not normalize away timezone, number or null differences merely to make the assertion pass. If a representation must change, version or coordinate that change with its consumers.
Evaluate lazy JDBC acquisition as a separate experiment
The 4.1 release notes introduce spring.datasource.connection-fetch. Setting it to lazy wraps an auto-configured pooled DataSource so obtaining a physical connection can wait until a JDBC statement is needed. The same notes document removed deprecations and build changes, including removal of the old layertools mode.
# Optional experiment after compatibility tests pass.
# Applies to the relevant auto-configured pooled DataSource.
spring:
datasource:
connection-fetch: lazy
This option is interesting for transaction scopes that do not always reach the database. It is not a larger connection pool, a faster SQL engine or permission to make transactions arbitrarily long. A service with a custom DataSource needs its own configuration review; adding the property does not prove the wrapper has been applied.
Spring Framework's connection-management documentation explains the role of LazyConnectionDataSourceProxy. Build a focused experiment around a real request path: measure pool acquisition, connection hold time, transaction outcome and latency with the setting enabled and disabled. Include a database failure at the point where the first statement executes.
For the hypothetical booking service, compare three paths: a validation failure before persistence, a read-only lookup and a booking transaction that writes data. Do not assume the lookup avoids SQL because a cache exists. Trace the actual call order and transaction boundaries. Delayed connection acquisition can also change where a connection failure becomes visible, which should be reflected in error handling.
Verify the boundaries around the application
| Boundary | Exercise | Required evidence |
|---|---|---|
| Security | Expired token and cross-user booking access | Same intended authentication and authorization outcome |
| PostgreSQL | Two attempts to reserve the same slot | One valid reservation and a defined loser response |
| Redis | Old serialized entry and unavailable cache | Defined compatibility and fallback behavior |
| Kafka | Duplicate event and interrupted consumer | Idempotent handling and observable recovery |
| Deployment | Start, drain and restart the packaged container | Correct readiness and bounded shutdown |
Keep old and new application instances in the compatibility discussion. During a rolling deployment they may share a cache, database and topic. An upgraded process that reads its own freshly written data correctly can still break when the previous version reads that same data. Test both directions before allowing incompatible writes.
The same principle applies to Redis cache formats. Namespace a deliberately incompatible cache representation and allow the previous namespace to expire under a defined policy. Do not flush unrelated cache data as a shortcut. For events, retain representative old messages so the new consumer is tested against history, not only against its own producer.
Test the artifact that will run
Run checks against the packaged jar or container after the unit and integration suites pass. Confirm the entrypoint, active profiles, certificates, service URLs and health endpoints. If the image build extracts jar layers, review the tooling command during the upgrade. A deployment can fail before business code runs even when the application tests are green.
Compare the two versions under equivalent resource limits and a representative request mix. Measure successful operations, error categories, p95 latency, memory, pool waits and consumer lag. Record warm-up separately. A shorter startup time is not evidence of better steady-state latency, and a lower average can hide a worse tail.
Define rollback in terms of compatibility
Keep schema changes backward compatible through the rollout window whenever possible. If a migration removes a column or changes an event format, redeploying the old container may no longer be a valid rollback. State exactly which writes each version can read and which operational action restores service if the new build fails.
Release with a small, observable traffic share and explicit stop conditions tied to user operations. Keep optional behavior changes, including lazy connection acquisition, independently configurable until their effects are understood. Connect application symptoms to traces and infrastructure signals using the approach in the observability guide.
The useful outcome is an upgrade record: selected versions, dependency decisions, preserved contracts, representative tests and a recovery path. Spring Boot 4.1 provides capabilities worth evaluating, but the strongest evidence of a good migration is that the service remains understandable when a dependency is slow, a client sends an old payload or a deployment has to be reversed.
