Versions and status. Tested on Spring Boot 4.2.0-M2 (milestone, 25 Sep 2026) and 4.1.1 (GA), JDK 25.0.4, Maven 3.9.11. 4.2 has no GA date yet; nothing below is final until it does, and this article will be updated at GA. Every transcript quoted below was produced by a real run or a real jar inspection; sections that come only from the release notes say so. Companion code: spring-boot-demo/boot42-preview.
The same starter name now means a different protocol
AMQP is the wire protocol message brokers speak. For years Spring Boot’sspring-boot-starter-amqp meant one thing: Spring AMQP talking AMQP 0.9.1 to RabbitMQ. In 4.2 the starter is split, and the old name is reused for something else.
The diagram shows the three starters and which protocol each speaks. The trap is the top row: the artifact you already depend on keeps its name but stops being the RabbitMQ 0.9.1 starter.
spring-boot-starter-amqp 4.1.1 HTTP 200 "Starter for using Spring AMQP and Rabbit MQ" -> spring-boot-amqp
spring-boot-starter-amqp 4.2.0-M2 HTTP 200 "Starter for generic AMQP 1.0 support" -> spring-boot-amqp
spring-boot-starter-rabbitmq 4.1.1 HTTP 404 (does not exist)
spring-boot-starter-rabbitmq 4.2.0-M2 HTTP 200 "Starter for using RabbitMQ messaging and streaming broker" -> spring-boot-rabbitmq
spring-boot-starter-amqp-rabbitmq 4.1.1 HTTP 404 (does not exist)
spring-boot-starter-amqp-rabbitmq 4.2.0-M2 HTTP 200 "Starter for using Spring AMQP with Rabbit MQ over AMQP 1.0 protocol" -> spring-boot-amqp-rabbitmq
Captured in 03-amqp-starters.txt.
Underneath, each module registers different auto-configuration. The 0.9.1 auto-configuration (RabbitAutoConfiguration) moved from the spring-boot-amqp module to a new spring-boot-rabbitmq module, while spring-boot-amqp now registers a generic AmqpAutoConfiguration:
# auto-configuration each module registers (AutoConfiguration.imports)
--- spring-boot-amqp 4.1.1
org.springframework.boot.amqp.autoconfigure.RabbitAutoConfiguration
org.springframework.boot.amqp.autoconfigure.health.RabbitHealthContributorAutoConfiguration
org.springframework.boot.amqp.autoconfigure.metrics.RabbitMetricsAutoConfiguration
--- spring-boot-amqp 4.2.0-M2
org.springframework.boot.amqp.autoconfigure.AmqpAutoConfiguration
--- spring-boot-rabbitmq 4.2.0-M2
org.springframework.boot.rabbitmq.autoconfigure.RabbitAutoConfiguration
org.springframework.boot.rabbitmq.autoconfigure.health.RabbitHealthContributorAutoConfiguration
org.springframework.boot.rabbitmq.autoconfigure.metrics.RabbitMetricsAutoConfiguration
--- spring-boot-amqp-rabbitmq 4.2.0-M2
org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpRabbitAutoConfiguration
org.springframework.boot.amqp.rabbitmq.autoconfigure.health.AmqpRabbitHealthContributorAutoConfiguration
Captured in 03-amqp-starters.txt.
What you should do. If you use RabbitMQ withRabbitTemplateand@RabbitListener, change the dependency tospring-boot-starter-rabbitmqbefore you try 4.2 — this is the migration the release notes describe for staying on 0.9.1. Choosespring-boot-starter-amqp-rabbitmqonly if you actually want AMQP 1.0. I could not run either against a broker in the sandbox, so the behaviour of the 1.0 client under load is not tested here.
Going deeper: why a split, and what I did not verify
@RabbitListener application starts cleanly after only swapping the artifact was not run; it needs a broker, and this module deliberately has none.
- Spring Boot 4.2.0-M1 release notes (wiki) — AMQP section
- Full output: 03-amqp-starters.txt
RestTemplate is now deprecated for removal, and the compiler will tell you where
RestTemplate is the HTTP client many applications started with. Spring Framework 7.1 deprecates it for removal, and Boot 4.2 follows by deprecating its helpers: RestTemplateBuilder, TestRestTemplate and the auto-configuration around them. “For removal” is stronger than plain deprecation: javac reports it as a [removal] warning that is on by default.
The file below is not part of the build. A script compiles it against each version with -Xlint:deprecation and commits what the compiler says.
import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.boot.resttestclient.TestRestTemplate;
import org.springframework.web.client.RestTemplate;
/** Not part of the build: compiled by scripts/capture-facts.sh so the compiler's own words are committed. */
class UsesRestTemplate {
RestTemplate viaBuilder(RestTemplateBuilder builder) {
return builder.build();
}
TestRestTemplate viaTest(TestRestTemplate t) {
return t;
}
RestTemplate plain() {
return new RestTemplate();
}
}
Source: UsesRestTemplate.java.
On 4.1.1 this compiles silently. On 4.2.0-M2 every use is flagged:--- Spring Boot 4.1.1
--- Spring Boot 4.2.0-M2
UsesRestTemplate.java:8: warning: [removal] RestTemplate in org.springframework.web.client has been deprecated and marked for removal
RestTemplate viaBuilder(RestTemplateBuilder builder) {
^
UsesRestTemplate.java:8: warning: [removal] RestTemplateBuilder in org.springframework.boot.restclient has been deprecated and marked for removal
RestTemplate viaBuilder(RestTemplateBuilder builder) {
^
Captured in 05-resttemplate-deprecations.txt.
The replacement isRestClient, built from the auto-configured RestClient.Builder. The version below compiles with no warnings under 4.2.0-M2 (output 06 ends immediately after its header line).
/** What the deprecated RestTemplateBuilder code turns into: an injected RestClient.Builder. */
@Configuration(proxyBeanMethods = false)
class RestClientReplacement {
@Bean
RestClient inventoryClient(RestClient.Builder builder) {
return builder.baseUrl("http://inventory.internal").build();
}
}
Source: RestClientReplacement.java, lines 7–17.
Going deeper: what else is deprecated, and test code
RestTemplateAutoConfiguration, RestTemplateObservationAutoConfiguration, the builder and its customizers, MockRestServiceServerAutoConfiguration and TestRestTemplate. Tests that inject TestRestTemplate are the ones that surprise people, because the warning appears in test sources nobody reads. The suggested replacement is RestTestClient.
For a step-by-step conversion including the exchange() trap, see the existing RestTemplate to RestClient migration guide.
Behind a proxy, the default header family changed
When your application runs behind a load balancer, the client’s real scheme and host arrive in headers. Two families exist: the olderX-Forwarded-Proto / X-Forwarded-Host, and the standard Forwarded header (RFC 7239). If the application misreads them it thinks every request is plain http on localhost, so redirects and generated links go to the wrong place.
The test starts the real application eight times under different settings, sends each header family, and records what request.getScheme() reports.
@Test
void recordsBehaviourPerStrategy() {
Transcript t = new Transcript("01-forwarded-headers");
Map<String, Map<?, ?>> results = new LinkedHashMap<>();
for (String strategy : new String[] { "none", "native", "framework" }) {
for (var e : HEADERS.entrySet()) {
try (ConfigurableApplicationContext ctx = BootRun.start("server.forward-headers-strategy=" + strategy)) {
Map<?, ?> seen = BootRun.whoAmI(ctx, e.getValue());
String key = strategy + "/" + e.getKey();
results.put(key, seen);
t.line(String.format("%-22s scheme=%s secure=%s host=%s port=%s", key, seen.get("scheme"),
seen.get("secure"), seen.get("serverName"), seen.get("serverPort").toString().equals("443") ? "443" : "<random>"));
}
}
}
Source: ForwardedHeadersTest.java, lines 26–40.
Here is the same test on 4.1.1. Withserver.forward-headers-strategy=framework, both header families are honoured:
framework/x-forwarded scheme=https secure=true host=shop.example.com port=443
framework/rfc7239 scheme=https secure=true host=shop.example.com port=443
Captured in 01-forwarded-headers-on-4.1.1.txt.
And on 4.2.0-M2, the same setting honours onlyX-Forwarded-*. A proxy that sends just the standard Forwarded header now looks like plain http:
framework/x-forwarded scheme=https secure=true host=shop.example.com port=443
framework/rfc7239 scheme=http secure=false host=localhost port=<random>
Captured in 01-forwarded-headers.txt.
The new propertyspring.mvc.forwarded-headers.header-format chooses the family. Its values are x-forwarded (default) and standard; picking standard flips the result, so the two rows swap:
framework+standard/x-forwarded scheme=http secure=false host=localhost port=<random>
framework+standard/rfc7239 scheme=https secure=true host=shop.example.com port=443
Captured in 01-forwarded-headers.txt.
A wrong value fails loudly — a wrong property name does not. I first guessedforwardedas the value. 4.2.0-M2 refuses to start with the message below. On 4.1.1 the same line was accepted and ignored, because the property does not exist there.header-format=forwarded -> IllegalArgumentException: No enum constant org.springframework.boot.webmvc.autoconfigure.WebMvcProperties.HeaderFormat.forwardedCaptured in 01-forwarded-headers.txt.
Going deeper: the native strategy, and what the four unchanged rows mean
none trusts neither header on both versions, which is the safe default. native delegates to Tomcat’s own remote-IP handling and only reads X-Forwarded-* on both versions, so it did not change. Only framework changed, because it is the one implemented by Spring rather than the container. The full eight-row table is in 01-forwarded-headers.txt; ports print as <random> because the test uses an ephemeral port.
The release notes add that framework now applies only to Spring MVC and WebFlux applications, not to arbitrary servlet code. That part was not tested here.
Buildpack builds can now cache in a registry, not just on your laptop
spring-boot:build-image builds a container image with Cloud Native Buildpacks. Buildpacks keep a build cache so the second build does not re-download everything. Until now that cache was a Docker volume or a directory on the machine doing the build — useless for CI runners that start empty every time.
In 4.2 the cache can be an image, pushed to and pulled from a registry, so an ephemeral runner can start warm. The diagram shows which of the three caches accepts which storage.
javap shows the split of the old single Cache class into LocalCache and ImageCache, and shows which plugin fields take which type.
# 04-buildpack-cache-classes (javap, spring-boot-buildpack-platform and spring-boot-maven-plugin)
--- buildpack-platform 4.1.1: classes matching Cache
Cache$Bind.class
Cache$Format.class
Cache$Volume.class
Cache.class
--- buildpack-platform 4.2.0-M2: classes matching Cache
Cache.class
ImageCache.class
LocalCache$Bind.class
LocalCache$Volume.class
LocalCache.class
Captured in 04-buildpack-cache-classes.txt.
--- javap -p Image, fields mentioning cache/workspace (4.2.0-M2)
LocalCacheInfo buildWorkspace;
CacheInfo buildCache;
LocalCacheInfo launchCache;
Captured in 04-buildpack-cache-classes.txt.
A configuration derived from those setters follows. Treat it as unverified: it has never been run against a registry.<!-- NOT EXECUTED: needs a Docker daemon and a registry. Element names come from the setters javap shows in
output/04-buildpack-cache-classes.txt (CacheInfo.setImage, ImageCacheInfo.setName). -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<buildCache>
<image>
<name>registry.example.com/team/demo-build-cache:latest</name>
</image>
</buildCache>
</image>
</configuration>
</plugin>
Source: build-image-cache-fragment.xml.
Going deeper: old versus new class layout
Cache class with nested Bind and Volume types and a Format enum. In 4.2.0-M2 Cache is an interface with static factories volume(..), bind(..) and image(..); LocalCache covers the first two and ImageCache the third. Anyone who used the buildpack platform classes directly (build tooling, custom plugins) will see a source-incompatible change; Maven and Gradle users only see the new option.
- Full javap output: 04-buildpack-cache-classes.txt
- Spring Boot 4.2.0-M1 release notes — image-based cache
Set and Map ordering: the note that did not reproduce
The M1 notes say Sets and Maps bound from configuration now use the binder consistently and keep the order of the configuration file. That would matter if your code assumed a sorted or arbitrary order. I tested the plain case — a properties file with keys deliberately out of alphabetical order — and 4.1.1 already behaved this way:app.regions=zulu,alpha,mike,bravo,yankee,charlie
app.limits.zulu=1
app.limits.alpha=2
app.limits.mike=3
app.limits.bravo=4
app.limits.yankee=5
app.limits.charlie=6
Source: order.properties.
Set class : java.util.LinkedHashSet
Set order : [zulu, alpha, mike, bravo, yankee, charlie]
Map class : java.util.LinkedHashMap
Map key order : [zulu, alpha, mike, bravo, yankee, charlie]
Captured in 02-set-and-map-binding-order-on-4.1.1.txt.
4.2.0-M2 gives the identical result (02-set-and-map-binding-order.txt). The note may describe cases I did not probe (other property sources, or raw reads throughEnvironment); for a record bound from a properties file, nothing changed between the two versions.
Everything else in 4.1 → 4.2.0-M2 that I only read about
These come from the M1 and M2 release notes and were not executed here. They are listed so you know what to grep your configuration for.| Area | Change (release notes) | Check in your project |
|---|---|---|
| Tomcat | server.tomcat.additional-tld-skip-patterns, redirect-context-root, use-relative-redirects move under server.tomcat.servlet.*; relative redirects now default to true | Search config for the old names |
| LDAP | Embedded LDAP with SSL enabled needs spring.ldap.embedded.ssl.bundle or it fails to start | Any test using embedded LDAP |
| Observability | management.observations.conventions=open-telemetry; common management.opentelemetry.otlp.endpoint|headers|compression; management.metrics.observations.ignored-meters removed; long-task timers no longer on by default | Dashboards that rely on long-task timers |
| Kafka | spring.kafka.template.admin, spring.kafka.listener.admin, spring.kafka.listener.await-async-results-on-stop; KafkaProperties.buildProperties deprecated for KafkaConfigBuilder | Custom code that calls buildProperties |
| Build | Janino dependency management removed; @DefaultValue accepts placeholders | Logback conditional config using Janino |
What to test now, in order
- Change the AMQP dependency first. It is the only change here that silently changes what a same-named artifact does.
- Compile with
-Xlint:removalon 4.2.0-M2 and count theRestTemplatewarnings; that number is your migration size. - If you run behind a proxy that sends
Forwarded, notX-Forwarded-*, setspring.mvc.forwarded-headers.header-format=standardand re-test redirects. - If you build images in CI, try the image build cache on a branch (unverified above).
- Skim the table above for property names you use.
Should you run a milestone at all? Not in production. Put 4.2.0-M2 on a branch, run your test suite, and file anything odd with the Spring team while it can still change. Milestone jars are on Maven Central, so no extra repository is needed. If your only reason is curiosity, the four experiments in this article take a few minutes and need no broker or Docker.
Further reading
- Companion code: boot42-preview (run
./scripts/run-all.shto regenerate every transcript) - RestTemplate to RestClient migration guide
- Spring Boot 4.2.0-M2 available now
- 4.2.0-M2 release notes and 4.2.0-M1 release notes
No Comments yet!