Spring Boot 4.2 is not out yet, but it is close enough that a test branch is worth an afternoon. The latest GA is 4.1.2. The latest 4.2 build is 4.2.0-RC1, which is on Maven Central as of 22 October 2026, sitting on top of Spring Framework 7.1.0-RC1. This post is a roundup of what changed across 4.2.0-M1, M2 and RC1, written for someone who has a Boot 4.1 application and wants to know what will happen to it.
One rule is applied to every item. If it can be run, it was run, against both 4.2.0-RC1 and 4.1.2, and the output is in a companion module you can clone. Items I could only read about in the release notes are marked as read, not run, so you can tell the two apart.
Versions this was written and run against. Spring Boot 4.2.0-RC1 (a release candidate, not GA) and 4.1.2 (the latest GA), Spring Framework 7.1.0-RC1, Micrometer 1.18.0-RC1, Spring Kafka 4.2.0-RC1 and JDK 25.0.5 (Temurin). GA status was checked againstmaven-metadata.xmlon Maven Central on 22 October 2026: 4.2.0-M1, 4.2.0-M2 and 4.2.0-RC1 are listed, 4.2.0 is not.
I did not find a published GA date for Boot 4.2. The Spring Framework 7.1 release-notes page, itself still marked as a preview, says 7.1.0 is scheduled for November 2026. Treat both as unconfirmed, and expect this post to need a pass when 4.2.0 ships.
Where 4.2 stands, in plain terms
A Spring Boot release goes through milestones (M1, M2), then one or more release candidates (RC1), then GA, which is the version you are meant to run in production. Milestones and release candidates are published so people can test them, and anything in them can still change. 4.2.0-RC1 is the first candidate. It is the first build where it makes sense to point a copy of your application at 4.2 and see what breaks.
The picture says one thing that matters for planning: RC1 is near the end of the line but not on it. There is no GA date to plan around, so the useful thing to do now is find the changes that will cost you time and fix them on a branch, not schedule the upgrade.
Everything below ran on the jars in this table. The versions come from the classpath of the module, not from a release announcement.
| Component | 4.2 build tested | Baseline |
|---|---|---|
| Spring Boot | 4.2.0-RC1 | 4.1.2 |
| Spring Framework | 7.1.0-RC1 | 7.0.10 |
| Spring Kafka | 4.2.0-RC1 | 4.1.2 |
| Micrometer | 1.18.0-RC1 | 1.17.2 |
| OpenTelemetry Java | 1.67.0 | 1.62.0 |
Going deeper: how every claim in this post is pinned to both versions
The module has two POMs, pom.xml on 4.2.0-RC1 and pom-41.xml on 4.1.2, because a parent version cannot be a property. Each test writes a transcript into output/ and asserts the same values it writes. The system property boot.label decides which version the test expects, so a transcript called 01-default-value-placeholders-on-4.1.2.txt is the 4.1.2 result of the same test that produced 01-default-value-placeholders.txt on RC1.
The effect is that every output block in this post is an assertion. If a later 4.2 build changes the behaviour, the build fails and the post is the thing that needs editing, which is the point.
- Regenerate everything with
run-all.sh(needs JDK 25 and Maven). - Spring Boot 4.2 release notes on the wiki are still a placeholder until GA; what I used is the M1, M2 and RC1 pages and the configuration changelog, listed under Further reading.
Start with the changes that stop you: they fail loudly
An upgrade problem that fails loudly is the cheap kind. You see it in the first minute. Two of the 4.2 changes behave like this.
Embedded LDAP with SSL turned on but no certificate bundle
Spring Boot can start a small in-memory LDAP directory for tests, called the embedded LDAP server. If you wrote spring.ldap.embedded.ssl.enabled=true and never said which certificate bundle to use, 4.1 started anyway, which the 4.2.0-M2 release notes describe as a plain-text listener. In 4.2 that combination refuses to start. The test below uses the real auto-configuration and nothing else: LdapEmbeddedSslTest.java.
new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(EmbeddedLdapAutoConfiguration.class))
.withPropertyValues("spring.ldap.embedded.base-dn=dc=example,dc=org",
"spring.ldap.embedded.port=0",
"spring.ldap.embedded.ssl.enabled=true")
.run(ctx -> {
On 4.2.0-RC1, from 03-ldap-embedded-ssl-without-bundle.txt:
spring.ldap.embedded.ssl.enabled=true, no bundle: context FAILED to start
java.lang.IllegalStateException
SSL is enabled but no SSL bundle has been set. An SSL bundle providing the server's certificate and private key is required for LDAPS
On 4.1.2 the same test, from 03-ldap-embedded-ssl-without-bundle-on-4.1.2.txt:
spring.ldap.embedded.ssl.enabled=true, no bundle: context STARTED
embedded server beans: 1
The fix is to set spring.ldap.embedded.ssl.bundle to a named SSL bundle that holds the server certificate and key. If your tests ran with SSL on and no bundle, they were testing a plain-text server while looking like they tested a secure one, so the new failure is arguably the more honest behaviour.
RestTemplate and the builder around it
Spring Framework 7.1 deprecates RestTemplate, and Boot 4.2 deprecates the classes that support it. This is a deprecation, not a removal: nothing stops compiling. But the compiler now says so, and the warning is marked [removal], which means “this will be deleted”. Framework’s notes put the removal in 8.0, which is not scheduled.
The class that triggers it is deliberately kept out of the build in UsesRestTemplate.java, and compile-broken.sh compiles it with -Xlint:deprecation against each version’s classpath:
public class UsesRestTemplate {
public String fetch(RestTemplateBuilder builder) {
RestTemplate rest = builder.build();
return rest.getForObject("http://localhost:8080/ping", String.class);
}
}
The compiler’s own message on 4.2.0-RC1, from 05-resttemplate-compiler-warnings.txt:
$ javac -Xlint:deprecation -cp <module classpath> -d target/broken src/broken/UsesRestTemplate.java
src/broken/UsesRestTemplate.java:11: warning: [removal] RestTemplateBuilder in org.springframework.boot.restclient has been deprecated and marked for removal
public String fetch(RestTemplateBuilder builder) {
^
src/broken/UsesRestTemplate.java:12: warning: [removal] RestTemplate in org.springframework.web.client has been deprecated and marked for removal
RestTemplate rest = builder.build();
^
2 warnings
exit: 0
On 4.1.2 the same command prints nothing and exits 0 (05-resttemplate-compiler-warnings-on-4.1.2.txt). That is the whole lesson: you will not see these warnings until the day you upgrade, and then you will see one per use. The replacement is RestClient, with RestClient.Builder instead of RestTemplateBuilder. The full walk-through is in the RestTemplate to RestClient migration guide.
Going deeper: the other classes deprecated alongside RestTemplate (read, not run)
The 4.2.0-M1 release notes list these as deprecated, all following from the Framework change:
RestTemplateAutoConfigurationandRestTemplateObservationAutoConfigurationRestTemplateBuilder,RestTemplateBuilderConfigurer,RestTemplateBuilderClientHttpRequestInitializerRestTemplateCustomizer,RestTemplateRequestCustomizer,ObservationRestTemplateCustomizerMockRestServiceServerAutoConfiguration,MockServerRestTemplateCustomizer,RootUriRequestExpectationManagerinspring-boot-restclient-testTestRestTemplate, withRestTestClientand@AutoConfigureRestTestClientas the replacement
Replacements named in the notes: RestClient.Builder for RestTemplateBuilder, RestClientCustomizer for RestTemplateCustomizer, and a RestClient.Builder configured in @RestClientTest tests that use MockRestServiceServer. For the test side, see RestTestClient in Spring Framework 7.
Then the ones that stay quiet: properties Boot does not recognise
The dangerous upgrade problems are the silent ones. Spring Boot reads configuration keys it knows about and ignores the rest without a word. That is convenient and it is also the cause of a whole class of bugs, because a key that stops meaning anything looks exactly like a key that works.
The clearest example in 4.2 is the new Kafka admin configuration. M2 added dedicated KafkaAdmin settings for KafkaTemplate and for listener containers, and a flag to wait for in-flight async results on shutdown. The test binds five of the new keys into KafkaProperties and records which ones the binder actually consumed: KafkaAdminBindingTest.java.
The figure shows the result. The code that produces it is a binder with a small handler that records every name it consumed. No Kafka broker is involved, which means this proves the keys bind, not that an admin client connects with them.
Set<String> consumed = new TreeSet<>();
BindHandler recorder = new BindHandler() {
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context,
Object result) {
if (context.getConfigurationProperty() != null) {
consumed.add(name.toString());
}
return result;
}
};
Binder binder = new Binder(new MapConfigurationPropertySource(PROPS));
KafkaProperties kp = binder.bind("spring.kafka", Bindable.of(KafkaProperties.class), recorder)
.orElseGet(KafkaProperties::new);
The 4.2.0-RC1 transcript, from 02-kafka-admin-binding.txt (the line with the object hash is trimmed):
properties supplied: 5
bound spring.kafka.listener.admin.client-id
bound spring.kafka.listener.admin.fail-fast
bound spring.kafka.listener.await-async-results-on-stop
bound spring.kafka.template.admin.client-id
bound spring.kafka.template.admin.fail-fast
bound = 5 of 5
listener.isAwaitAsyncResultsOnStop() = true
template admin client-id = template-admin
template admin fail-fast = true
listener admin client-id = listener-admin
listener admin fail-fast = true
And on 4.1.2, from 02-kafka-admin-binding-on-4.1.2.txt:
properties supplied: 5
IGNORED spring.kafka.listener.admin.client-id
IGNORED spring.kafka.listener.admin.fail-fast
IGNORED spring.kafka.listener.await-async-results-on-stop
IGNORED spring.kafka.template.admin.client-id
IGNORED spring.kafka.template.admin.fail-fast
bound = 0 of 5
listener.isAwaitAsyncResultsOnStop() = no such method
template.getAdmin() = no such method
Why this matters in practice. The risk is configuration that moves faster than code. If you roll 4.2 configuration out through a shared config repository while some services are still on 4.1, those services ignore the new keys and nothing tells you. The reverse is just as quiet: a key that 4.2 removed is ignored on 4.2 without complaint, which is what the next section shows.
The properties migrator, and what it does not say
Boot ships a helper for exactly this, spring-boot-properties-migrator. Add it as a runtime dependency, start the application, and it reports keys that were renamed or removed, temporarily mapping renamed ones for you. Remove it again before you ship. The test sets five 4.2-relevant keys and captures what the migrator prints: MigratorTest.java.
app.setDefaultProperties(java.util.Map.of(
"server.tomcat.use-relative-redirects", "false",
"server.tomcat.redirect-context-root", "false",
"server.tomcat.additional-tld-skip-patterns", "*.jar",
"spring.mvc.pathmatch.matching-strategy", "path_pattern_parser",
"management.metrics.observations.ignored-meters", "http.server.requests"));
What it printed on 4.2.0-RC1, from 04-properties-migrator-report.txt:
The use of configuration keys that have been renamed was found in the environment:
Property source 'defaultProperties':
Key: server.tomcat.additional-tld-skip-patterns
Replacement: server.tomcat.servlet.additional-tld-skip-patterns
Key: server.tomcat.redirect-context-root
Replacement: server.tomcat.servlet.redirect-context-root
Key: server.tomcat.use-relative-redirects
Replacement: server.tomcat.servlet.use-relative-redirects
The use of configuration keys that are no longer supported was found in the environment:
Property source 'defaultProperties':
Key: spring.mvc.pathmatch.matching-strategy
Reason: Path matching is no longer configurable
Three Tomcat keys moved under server.tomcat.servlet.*, and the migrator maps them for you with a warning. spring.mvc.pathmatch.matching-strategy is reported at error level because there is no replacement: path matching is no longer configurable. Both are useful.
Look at what is missing. The fifth key I set, management.metrics.observations.ignored-meters, was removed in M2, and the migrator said nothing about it. It did not appear in the report even though it was in the environment. If you had used that key to exclude meters, the exclusion silently stopped.
The migrator is a flashlight, not a guarantee. It reports what its metadata knows about. A removed key can fall outside that and produce no output, so a clean migrator report does not mean a clean upgrade. Read the configuration changelog for your version pair as well, and search your own configuration for each removed key by name.
Going deeper: the 4.2.0-M1 to M2 configuration changelog, as listed (read, not run)
From the wiki page “Configuration Property Changes: 4.2.0-M1 to 4.2.0-M2”. Only the keys I ran against are marked as run elsewhere in this post.
| Kind | Key | Note |
|---|---|---|
| Default changed | spring.test.database.replace | any to non-test |
| Default changed | server.tomcat.remoteip.internal-proxies | same CIDR ranges in a different order |
| Deprecated | server.tomcat.additional-tld-skip-patterns, redirect-context-root, use-relative-redirects | moved to server.tomcat.servlet.* (run: migrator report above) |
| Deprecated | management.appoptics.metrics.export.* (six keys) | no replacement listed |
| Removed | management.metrics.observations.ignored-meters | listed replacement: include-active-long-task-timer |
| Removed | management.prometheus.metrics.export.pushgateway.base-url | replacement: ...pushgateway.address |
| Added | server.tomcat.servlet.use-relative-redirects | default true, to match Tomcat |
| Added | management.observations.conventions, management.opentelemetry.otlp.*, management.tracing.mdc.* | run: see the OpenTelemetry post |
| Added | spring.kafka.template.admin.*, spring.kafka.listener.admin.* | run: binding only |
| Added | spring.ldap.ssl.bundle, spring.ldap.ssl.enabled, spring.ldap.embedded.authentication-required-operation-types | read, not run |
| Added | spring.graphql.http.methods, spring.graphql.http.sse.methods | default POST, read, not run |
From the M1 page, the same way: spring.mvc.pathmatch.matching-strategy and spring.rabbitmq.ssl.verify-hostname were deprecated, and server.*.forwarded-headers.header-format, spring.mvc.forwarded-headers.* and spring.webflux.forwarded-headers.* were added. The forwarded-header and AMQP changes were run in the earlier 4.2 preview post.
Going deeper: two defaults that change behaviour without a key changing (read, not run)
These are in the notes and I did not execute them, so weigh them accordingly.
server.tomcat.servlet.use-relative-redirectsnow defaults totrue, which matches Tomcat itself. If something downstream parsed absoluteLocationheaders from redirects, check it. The M2 notes name the property to flip it back; the page prints it with a stray hyphen, so copy the key from the changelog table instead.spring.test.database.replacewent fromanytonon-test. In@DataJpaTeststyle tests this changes when Boot swaps your configured database for an embedded one, so a test that relied on the swap can start talking to a real database.
Putting the upgrade surprises side by side makes the pattern visible. Every item below was either run in this module or the OpenTelemetry module, or is marked as read.
The left column costs you a minute. The right column is what a test branch is for: run your own integration tests and your dashboards against RC1, not just the build.
New things that are worth the upgrade
@DefaultValue now understands placeholders
When you bind configuration to a Java record, @DefaultValue supplies the value used when nothing is configured. Until 4.2 that text was taken literally. From M2 it may contain a property placeholder such as ${demo.region:eu}, which is resolved while binding. Two small records in the module make the difference visible: DemoProperties.java and DemoPort.java.
@ConfigurationProperties("demo")
public record DemoProperties(
@DefaultValue("${demo.region:eu}-primary") String zone,
@DefaultValue("static-value") String plain) {
}
On 4.2.0-RC1, from 01-default-value-placeholders.txt:
no demo.* properties set: zone = eu-primary plain = static-value
demo.region=ap: zone = ap-primary
int property, placeholder default: number = 8080
And on 4.1.2, from 01-default-value-placeholders-on-4.1.2.txt:
no demo.* properties set: zone = ${demo.region:eu}-primary plain = static-value
demo.region=ap: zone = ${demo.region:eu}-primary
int property, placeholder default: context FAILED to start
root cause: java.lang.NumberFormatException
For input string: "${demoport.number:8080}"
On 4.1.2 the string property contains the literal text ${demo.region:eu}-primary, which would have been a confusing thing to find in a hostname, and an int with a placeholder default fails at start-up with a NumberFormatException. On 4.2 both resolve. If you had worked around the old behaviour by building the default in code, you can now leave it in the annotation.
Kafka gets its own admin settings
The five keys in the earlier figure are the visible part of M2’s Kafka work. spring.kafka.template.admin.* and spring.kafka.listener.admin.* each accept the same eighteen settings (client id, close and operation timeouts, fail-fast, SSL options and so on) so a template or a listener container can use its own KafkaAdmin instead of the shared one. spring.kafka.listener.await-async-results-on-stop makes a container wait for in-flight async listener results during shutdown. Only the binding was run here; no broker was started, so whether an admin client then connects with those settings is from the notes.
Other changes in the notes
The rest is shorter, and I have kept the sourcing honest in the table. Anything marked read is from the release notes and was not executed.
| Change | Where | Run? |
|---|---|---|
| OpenTelemetry semantic conventions, shared OTLP settings, MDC keys, long task timer | M2 | run, in the OpenTelemetry post |
AMQP 1.0 starters (spring-boot-starter-rabbitmq keeps 0.9, spring-boot-starter-amqp-rabbitmq is 1.0) | M1 | run, in the earlier 4.2 preview post |
Forwarded headers: X-Forwarded-* is the default family under FRAMEWORK | M1 | run, in the earlier 4.2 preview post |
Embedded LDAPS bundle and spring.ldap.ssl.bundle for the client | M2 | failure case run, the rest read |
New default buildpack builder paketobuildpacks/ubuntu-resolute-builder (Ubuntu 26.04) | RC1 | read, needs Docker |
Default run image ubuntu-resolute-run-tiny has no shell | RC1 | read, needs Docker |
| Build cache can be an image in a registry | M1 | read, needs Docker and a registry |
@AutoConfigureMetrics(useGlobalRegistry = true) to opt back into the global registry in tests | M1 | read |
tools jar mode gets an sbom command | M2 | read |
| Removal of everything deprecated in 4.1 | M1 | read; the notes do not list the items |
The builder change is the one to look at before CI does it for you. If your pipeline builds images with the Boot plugin and no explicit builder, RC1 moves you to an Ubuntu 26.04 base, and the default run image has no shell, so you cannot open a shell inside the container. The RC1 notes say to setbuilderback topaketobuildpacks/builder-noble-java-tinyto keep the old behaviour, and to pin bothbuilderandrunImagefor reproducible images. I read this; I did not run a build.
Going deeper: dependency upgrades named in the milestone notes (read, not run)
These are the versions the M1 and M2 notes list for some of the components. They are milestone-era numbers, and the RC1 page still has “TBD” for its dependency section, so confirm against the release you actually pull.
- Spring: Framework 7.1.0-M1/M2, Security 7.2.0-M1, Data 2026.1.0-M1/M2, Batch 6.1.0-M1/M2, Integration 7.2.0-M1/M2, Kafka 4.2.0-M1/M2, AMQP 4.2.0-M1/M2, HATEOAS 3.2.0-M1/M2, GraphQL 2.1.0-M1, Web Services 5.1.0-M1
- Observability: Micrometer 1.18.0-M1/M2, Micrometer Tracing 1.8.0-M1/M2, OpenTelemetry 1.64.0 (M1), Prometheus client 1.8.0
- Others from M1/M2: Kotlin 2.4.10 then 2.4.20, Lettuce 7.6.0 then 7.7.0, MongoDB 5.9.1 then 5.11.1, Flyway 12.11.0, Log4j2 2.26.1, JUnit Jupiter 6.1.3, SnakeYAML 2.7, Selenium 4.46.0 then 4.49.0
- On the RC1 classpath of the modules here: Framework 7.1.0-RC1, Kafka 4.2.0-RC1, Micrometer 1.18.0-RC1, Micrometer Tracing 1.8.0-RC1, OpenTelemetry 1.67.0
Spring Framework 7.1: the part that reaches your application
Boot 4.2 sits on Spring Framework 7.1, and several 7.1 changes surface in ordinary Boot applications without any Boot property involved. The Framework 7.1 release notes are a preview page, scheduled for November 2026, so this section is the one most likely to change. None of it was run: the table reports what the notes say and how it could bite.
| Change in 7.1 | What it means in an application |
|---|---|
RestTemplate deprecated, removal planned for 8.0 | Compiler warnings now; plan the move to RestClient (run, above) |
5xx errors handled by ResponseEntityExceptionHandler, ResponseStatusExceptionResolver or DefaultHandlerExceptionResolver are now recorded on the HTTP server observation | The error tag on http.server.requests shows the exception name instead of none for those responses. Alerts that filter on error="none" change meaning |
A timeout of 0 now means no timeout for connect, read and block timeouts (Reactor and Jetty request factories, RSocketServiceProxyFactory, WebTestClient) | A 0 that used to be rejected or treated as a default becomes unbounded. Negative and sub-millisecond values are now rejected |
ProblemDetail.instance is no longer set to the request path | Clients that read instance from error bodies get nothing unless you set it or use an ErrorResponse.Interceptor |
HttpMessageConverters no longer auto-detects JAXB converters on servers | XML endpoints that relied on it need withXmlConverter(new Jaxb2RootElementHttpMessageConverter()) in a WebMvcConfigurer |
| WebFlux multipart reader defaults: 1MB per part on disk, 50 parts | Large multipart uploads can start failing; raise the limits through ServerCodecConfigurer |
| Default test context configuration is now detected for all superclasses and enclosing classes | Tests can pick up unexpected beans; the notes say a warning has been logged since 7.0.6 |
HandlerMappingIntrospector removed; ForwardedHeaderFilter and ForwardedHeaderTransformer default constructors deprecated for removal | Only matters if you reference them directly; the notes say HandlerMappingIntrospector was internal to Spring Security |
New: HTTP QUERY method, MultipartHttpMessageConverter, Problem Details configuration for MVC, HTML error pages, JsonUnit support in spring-test | Optional; nothing to do on upgrade |
| Baselines: Jackson 3.1, Hibernate ORM 7.3, HtmlUnit 5.4 | Check your own dependency management if you override Boot’s versions |
Going deeper: why the observation change is the one dashboards notice first (read, not run)
The error tag on the HTTP server timer is what many error-rate alerts key off. Because 7.1 records the exception on 5xx responses produced by those three handlers, the tag value changes from the literal word none to an exception class name for exactly the responses you care about. A custom ObservationConvention can ignore specific errors, per the notes. The 4xx case is unchanged: still not recorded.
The OpenTelemetry post covers the other half of the dashboard story, because Boot 4.2 can also rename the whole meter. If you change both in one deploy you will not know which one broke the panel: Spring Boot 4.2 OpenTelemetry.
An upgrade checklist, in order
Order matters, because the early steps are cheap and make the later ones easier to read. Do this on a branch against 4.2.0-RC1, not against production.
- Build the branch with
-Xlint:deprecationand count the[removal]warnings. That number is the size of yourRestTemplatejob. - Add
spring-boot-properties-migrator, start the application with each profile you ship, and fix everything it reports. Then search your configuration, by name, for every key in the removed list above, because the migrator does not report all of them. - Grep your configuration repository for keys you expect 4.2 to read (Kafka admin, LDAP SSL, OpenTelemetry) and ask which services receiving them are still on 4.1. Those services ignore them silently.
- If you use embedded LDAP with SSL in tests, give it a bundle.
- If CI builds images with the Boot plugin, decide on the builder and run image explicitly before the new default decides for you.
- Run your integration tests and read the HTTP metrics and the
errortag on a 5xx response with your real handlers. - Treat the OpenTelemetry settings as a separate change with their own rollout; do not switch conventions in the same deploy as the version bump.
- Remove the migrator, then re-run once more on GA when it exists, because RC1 can still change.
Should you move to 4.2.0-RC1 now? On a branch, yes: the failures worth finding early are the quiet ones, and they only show up when you run your own code. In production, no. A release candidate can still change, there is no published GA date, and the second post in this pair found a start-up failure on RC1 for the documented value of one of the new properties. If you only need a supported release today, 4.1.2 is GA and does not carry any of this.
Further reading
- The companion module: boot42-rc1-checklist (transcripts in
output/, regenerated byscripts/run-all.sh). - On this site: Spring Boot 4.2 OpenTelemetry, Spring Boot 4.2 Preview: AMQP 1.0, Buildpack Image Cache, RestTemplate to RestClient migration guide and Spring Boot versions.
- Spring Boot 4.2.0-M1 release notes, 4.2.0-M2 and 4.2.0-RC1.
- Configuration property changes, 4.2.0-M1 to 4.2.0-M2.
- Spring Framework 7.1 release notes (preview).
- Spring Boot 4.2 reference: Observability.
No Comments yet!