Skip to main content

Spring Boot 4.2 Preview: AMQP 1.0, Buildpack Image Cache and What to Test Now

Spring Boot 4.2.0-M2 run next to 4.1.1: the spring-boot-starter-amqp name that changes protocol, RestTemplate deprecated for removal, the forwarded-header default that flips, the buildpack image cache and what to test first. Preview article, updated at GA.

Spring Boot 4.2 is not out yet. Two milestones are: 4.2.0-M1 and 4.2.0-M2 (published 25 September 2026). A milestone is a public preview — the APIs can still move — but it is also the cheapest moment to find out what your application will trip over, because you can still tell the Spring team. This article runs the parts of 4.2 that change behaviour against a real project, next to 4.1.1, and tells you which changes to test first. You do not need to know Spring Boot internals to follow it. Each section states what changed, shows the smallest experiment that proves it, and links to the exact file it came from. Sections with deeper material have a collapsed “going deeper” panel; open only the ones you need.
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’s spring-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.
Same name, different meaningspring-boot-starter-amqp4.1.1RabbitMQ, AMQP 0.9.1spring-boot-starter-amqp4.2.0-M2generic AMQP 1.0 (any broker)spring-boot-starter-rabbitmqnew in 4.2RabbitMQ, AMQP 0.9.1spring-boot-starter-amqp-rabbitmqnew in 4.2 → RabbitMQ, AMQP 1.0Upgrading without editing the POM moves a RabbitMQ 0.9.1 application to a starter that no longer configures RabbitMQ.
The POMs on Maven Central say so in their own descriptions. This is the output of a script that reads them, not a paraphrase of the release notes.
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 with RabbitTemplate and @RabbitListener, change the dependency to spring-boot-starter-rabbitmq before you try 4.2 — this is the migration the release notes describe for staying on 0.9.1. Choose spring-boot-starter-amqp-rabbitmq only 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
The milestone notes list two new capabilities: generic support built on Apache Qpid Proton for any AMQP 1.0 broker, and RabbitMQ-specific 1.0 support with extra features. AMQP 0.9.1 and 1.0 are different protocols that share a name, not versions of one protocol, which is why one starter cannot serve both and why the 0.9.1 code got its own module. The three module names and their auto-configuration classes are read from the published jars (the output above). Whether a @RabbitListener application starts cleanly after only swapping the artifact was not run; it needs a broker, and this module deliberately has none.

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 is RestClient, 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
The M1 notes list the complete set: 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 older X-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. With server.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 only X-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 property spring.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 guessed forwarded as 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.forwarded

Captured 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.
Where each buildpack cache can live (4.2.0-M2)buildCachevolumebindimage (new)buildWorkspacevolumebindno imagelaunchCachevolumebindno imageOnly the build cache can be an image; the other two stay local.
I could not run a build (no Docker daemon here), so the claim comes from the plugin’s compiled classes instead. 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
In 4.1.1 the platform library has one 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.

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 through Environment); 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.
AreaChange (release notes)Check in your project
Tomcatserver.tomcat.additional-tld-skip-patterns, redirect-context-root, use-relative-redirects move under server.tomcat.servlet.*; relative redirects now default to trueSearch config for the old names
LDAPEmbedded LDAP with SSL enabled needs spring.ldap.embedded.ssl.bundle or it fails to startAny test using embedded LDAP
Observabilitymanagement.observations.conventions=open-telemetry; common management.opentelemetry.otlp.endpoint|headers|compression; management.metrics.observations.ignored-meters removed; long-task timers no longer on by defaultDashboards that rely on long-task timers
Kafkaspring.kafka.template.admin, spring.kafka.listener.admin, spring.kafka.listener.await-async-results-on-stop; KafkaProperties.buildProperties deprecated for KafkaConfigBuilderCustom code that calls buildProperties
BuildJanino dependency management removed; @DefaultValue accepts placeholdersLogback 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:removal on 4.2.0-M2 and count the RestTemplate warnings; that number is your migration size.
  • If you run behind a proxy that sends Forwarded, not X-Forwarded-*, set spring.mvc.forwarded-headers.header-format=standard and 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

No Comments yet!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.