<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ankur Mhatre</title>
    <description>The latest articles on DEV Community by Ankur Mhatre (@ankurm).</description>
    <link>https://dev.to/ankurm</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3956307%2F6315ce87-9cc8-4e37-a1f2-cf336414f2f5.png</url>
      <title>DEV Community: Ankur Mhatre</title>
      <link>https://dev.to/ankurm</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ankurm"/>
    <language>en</language>
    <item>
      <title>Spring Framework 6 to 7 Migration Guide: Breaking Changes, Deprecated APIs, and Upgrade Checklist</title>
      <dc:creator>Ankur Mhatre</dc:creator>
      <pubDate>Wed, 24 Jun 2026 19:27:58 +0000</pubDate>
      <link>https://dev.to/ankurm/spring-framework-6-to-7-migration-guide-breaking-changes-deprecated-apis-and-upgrade-checklist-3bf6</link>
      <guid>https://dev.to/ankurm/spring-framework-6-to-7-migration-guide-breaking-changes-deprecated-apis-and-upgrade-checklist-3bf6</guid>
      <description>&lt;p&gt;I have spent the last few months taking a mid-sized Spring service from &lt;strong&gt;Spring Framework 6.2&lt;/strong&gt; to &lt;strong&gt;Spring Framework 7.0&lt;/strong&gt;, and the thing nobody tells you up front is this: most of the migration content you find is actually about &lt;em&gt;Spring Boot&lt;/em&gt;. Boot 4 gets all the headlines. But Boot 4 sits &lt;em&gt;on top of&lt;/em&gt; Framework 7, and the changes that broke my build, my tests, and (twice) my production behaviour came from the core framework — bean lifecycle, AOT metadata, the Jakarta cutover, and a pile of quietly removed APIs.&lt;/p&gt;

&lt;p&gt;This guide is about the layer underneath Boot. If you maintain a library, a non-Boot Spring application, or you just want to understand &lt;em&gt;why&lt;/em&gt; Boot 4 forces certain changes, this is the migration you need to read. Framework 7.0 went GA on &lt;strong&gt;13 November 2025&lt;/strong&gt;, and everything below is verified against the official 7.0 release notes — not guessed from a beta.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, the mental model: Framework 7 is not Boot 4
&lt;/h2&gt;

&lt;p&gt;Spring Boot 4.0 is the product most teams upgrade. But Boot 4 &lt;em&gt;depends on&lt;/em&gt; Spring Framework 7.0 the same way Boot 3 depended on Framework 6. Almost every "Boot 4 broke my code" story traces back to a Framework 7 decision: the Jakarta EE 11 baseline, the removal of &lt;code&gt;javax&lt;/code&gt; annotation support, the JSpecify null-safety migration, the AOT reachability-metadata change. If you understand Framework 7, Boot 4 stops being mysterious.&lt;/p&gt;

&lt;p&gt;A useful rule while migrating: when a class lives in &lt;code&gt;org.springframework.*&lt;/code&gt;, it is a Framework 7 concern and this guide covers it. When it lives in &lt;code&gt;org.springframework.boot.*&lt;/code&gt;, check the &lt;a href="https://ankurm.com/spring-boot-3-to-4-migration-guide/" rel="noopener noreferrer"&gt;Spring Boot 3 to 4 migration guide&lt;/a&gt; instead. The two upgrades happen together, but they fail for different reasons.&lt;/p&gt;

&lt;h2&gt;
  
  
  Baseline upgrades: what you need before you start
&lt;/h2&gt;

&lt;p&gt;Framework 7.0 keeps a &lt;strong&gt;JDK 17 baseline&lt;/strong&gt; — you are &lt;em&gt;not&lt;/em&gt; forced onto Java 25 — but it recommends JDK 25 (the current LTS) and adopts Jakarta EE 11. The minimum versions moved up across the board:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Spring Framework 6.2&lt;/th&gt;
&lt;th&gt;Spring Framework 7.0&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JDK&lt;/td&gt;
&lt;td&gt;17 (baseline), 21 recommended&lt;/td&gt;
&lt;td&gt;17 (baseline), &lt;strong&gt;25 recommended&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jakarta EE&lt;/td&gt;
&lt;td&gt;9/10&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Servlet&lt;/td&gt;
&lt;td&gt;6.0 (Tomcat 10.1, Jetty 12)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;6.1&lt;/strong&gt; (Tomcat 11, Jetty 12.1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JPA&lt;/td&gt;
&lt;td&gt;3.1&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;3.2&lt;/strong&gt; (Hibernate ORM 7.1/7.2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bean Validation&lt;/td&gt;
&lt;td&gt;3.0&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;3.1&lt;/strong&gt; (Hibernate Validator 9.0/9.1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kotlin&lt;/td&gt;
&lt;td&gt;1.x/2.0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.2&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GraalVM&lt;/td&gt;
&lt;td&gt;22.3+&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;25&lt;/strong&gt; (new metadata format)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JUnit&lt;/td&gt;
&lt;td&gt;5 (Jupiter)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jackson&lt;/td&gt;
&lt;td&gt;2.x&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;3.x&lt;/strong&gt; default, 2.x deprecated&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The one that surprised me: &lt;strong&gt;Undertow support is gone&lt;/strong&gt;. Undertow does not yet support Servlet 6.1, so Spring dropped its Undertow-specific WebSocket and WebFlux HTTP classes. If you run Undertow, you are blocked on the migration until Undertow ships a Servlet 6.1 release — plan around that early, because there is no flag to work past it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Jakarta cutover that actually bites: javax annotations
&lt;/h2&gt;

&lt;p&gt;Most teams finished the &lt;code&gt;javax.*&lt;/code&gt; → &lt;code&gt;jakarta.*&lt;/code&gt; move during the Boot 2→3 era. But Framework 6 quietly kept a &lt;em&gt;compatibility bridge&lt;/em&gt; for two annotation packages. Framework 7 removes it. Support for &lt;code&gt;javax.annotation&lt;/code&gt; and &lt;code&gt;javax.inject&lt;/code&gt; annotations is &lt;strong&gt;completely gone&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// These no longer do anything in Spring 7 — silently ignored:&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;javax.annotation.PostConstruct&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;javax.annotation.Resource&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;javax.inject.Inject&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Replace with the Jakarta equivalents:&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;jakarta.annotation.PostConstruct&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;jakarta.annotation.Resource&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;jakarta.inject.Inject&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the most dangerous change in the whole release, because it &lt;strong&gt;fails silently&lt;/strong&gt;. Code using &lt;code&gt;@javax.annotation.PostConstruct&lt;/code&gt; still compiles (the annotation is on the classpath via some transitive dependency), but Spring 7 no longer recognises it, so your init method just never runs. No exception, no log line. Grep your entire codebase for &lt;code&gt;javax.annotation&lt;/code&gt; and &lt;code&gt;javax.inject&lt;/code&gt; before you trust your test suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Removed APIs (these are hard compile errors — the easy part)
&lt;/h2&gt;

&lt;p&gt;Compile errors are the friendly failures: the compiler tells you exactly where to look. Here is what Framework 7 deleted outright.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Removed&lt;/th&gt;
&lt;th&gt;Replacement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;spring-jcl&lt;/code&gt; module&lt;/td&gt;
&lt;td&gt;Apache Commons Logging 1.3+ (transitive, usually transparent)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;javax.annotation&lt;/code&gt; / &lt;code&gt;javax.inject&lt;/code&gt; support&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;jakarta.annotation&lt;/code&gt; / &lt;code&gt;jakarta.inject&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ListenableFuture&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CompletableFuture&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;suffixPatternMatch&lt;/code&gt;, &lt;code&gt;trailingSlashMatch&lt;/code&gt;, &lt;code&gt;favorPathExtension&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;PathPattern&lt;/code&gt;-based matching (the default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PathExtensionContentNegotiationStrategy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Explicit content negotiation config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Undertow WebSocket / WebFlux support&lt;/td&gt;
&lt;td&gt;Tomcat 11+ or Jetty 12.1+ (Servlet 6.1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Theme&lt;/code&gt; support&lt;/td&gt;
&lt;td&gt;External theming / i18n libraries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OkHttp3 client support&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;JdkClientHttpRequestFactory&lt;/code&gt; or Reactor Netty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;webjars-locator-core&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;webjars-locator-lite&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;ListenableFuture&lt;/code&gt; removal is the one I hit most. Any async method that returned &lt;code&gt;ListenableFuture&amp;lt;T&amp;gt;&lt;/code&gt; — common in older &lt;code&gt;@Async&lt;/code&gt; code and in Kafka/AMQP callback chains — now has to return &lt;code&gt;CompletableFuture&amp;lt;T&amp;gt;&lt;/code&gt;. The good news is &lt;code&gt;CompletableFuture&lt;/code&gt; has a richer API, so the rewrite usually simplifies the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deprecated APIs: what still works today but won't in 7.1 / 7.2
&lt;/h2&gt;

&lt;p&gt;Deprecations are your migration runway. Nothing here breaks on the day you upgrade to 7.0, but each one has a removal timeline, and ignoring them just defers the pain. The ones that matter for application code:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Deprecated in 7.0&lt;/th&gt;
&lt;th&gt;Migrate to&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;RestTemplate&lt;/code&gt; (docs-level; &lt;code&gt;@Deprecated&lt;/code&gt; in 7.1)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RestClient&lt;/code&gt; / HTTP interface clients&lt;/td&gt;
&lt;td&gt;See "the state of HTTP clients" from the Spring team&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jackson 2.x support&lt;/td&gt;
&lt;td&gt;Jackson 3.x (&lt;code&gt;tools.jackson&lt;/code&gt; package)&lt;/td&gt;
&lt;td&gt;Auto-detection off in 7.1, removed in 7.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;AntPathMatcher&lt;/code&gt; for request mappings&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PathPattern&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Now supports leading &lt;code&gt;/**/&lt;/code&gt; segments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;PathMatcher&lt;/code&gt; in Spring MVC&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PathPattern&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Affects Spring Security path alignment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JUnit 4 support in TestContext&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;SpringExtension&lt;/code&gt; + JUnit Jupiter&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;SpringRunner&lt;/code&gt;, &lt;code&gt;SpringClassRule&lt;/code&gt; etc. deprecated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;mvc:*&amp;gt;&lt;/code&gt; XML namespace&lt;/td&gt;
&lt;td&gt;Java config&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;bean&amp;gt;&lt;/code&gt; namespace is NOT deprecated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;document&lt;/code&gt; / &lt;code&gt;feed&lt;/code&gt; view classes (PDF, RSS, XLS)&lt;/td&gt;
&lt;td&gt;Render with external libraries in handlers&lt;/td&gt;
&lt;td&gt;Will be removed in a future version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring nullness annotations (JSR 305)&lt;/td&gt;
&lt;td&gt;JSpecify annotations&lt;/td&gt;
&lt;td&gt;See Null Safety section below&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My advice: treat the Jackson 2.x and &lt;code&gt;RestTemplate&lt;/code&gt; deprecations as the highest-priority follow-ups. Both have a hard removal date (Jackson 2.x in 7.2), and both touch a lot of surface area. I cover the Jackson side in detail in the &lt;a href="https://ankurm.com/jackson-3-migration-guide/" rel="noopener noreferrer"&gt;Jackson 2 to Jackson 3 migration guide&lt;/a&gt;, and the client side in &lt;a href="https://ankurm.com/spring-boot-4-http-service-clients/" rel="noopener noreferrer"&gt;Spring Boot 4 HTTP Service Clients&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bean lifecycle and registration changes
&lt;/h2&gt;

&lt;p&gt;This is the section I wish I had read first, because the changes are subtle and they interact with AOT.&lt;/p&gt;

&lt;h3&gt;
  
  
  Programmatic registration gets a first-class API: BeanRegistrar
&lt;/h3&gt;

&lt;p&gt;Before 7.0, registering beans programmatically meant a &lt;code&gt;BeanDefinitionRegistryPostProcessor&lt;/code&gt; and hand-built &lt;code&gt;RootBeanDefinition&lt;/code&gt; objects — verbose, and invisible to the AOT engine. Framework 7 introduces the &lt;code&gt;BeanRegistrar&lt;/code&gt; contract, which is both cleaner and &lt;strong&gt;AOT-analysable at build time&lt;/strong&gt; (so it works in GraalVM native images):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyBeanRegistrar&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;BeanRegistrar&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BeanRegistry&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Environment&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerBean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"foo"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Foo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerBean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"bar"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Bar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prototype&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lazyInit&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Custom description"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;supplier&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Bar&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Foo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;))));&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;matchesProfiles&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"baz"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerBean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Baz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;supplier&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Baz&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello World!"&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You import it with &lt;code&gt;@Import(MyBeanRegistrar.class)&lt;/code&gt; on a configuration class. If you have ever written conditional or loop-driven bean registration, this replaces a lot of brittle low-level code — and unlike the old approach, it does not silently break native-image builds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Proxy defaulting is now consistently CGLIB — and @Proxyable lets you opt out
&lt;/h3&gt;

&lt;p&gt;In 6.x, proxy-type defaulting was inconsistent: Boot defaulted to CGLIB, but some processors (like &lt;code&gt;@Async&lt;/code&gt;) did their own thing. As of 7.0, &lt;strong&gt;CGLIB defaulting is applied consistently to all proxy processors&lt;/strong&gt;. If you relied on a JDK dynamic proxy being created for an &lt;code&gt;@Async&lt;/code&gt; bean, that assumption may no longer hold. The new &lt;code&gt;@Proxyable&lt;/code&gt; annotation gives you per-bean control:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="nd"&gt;@Proxyable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;INTERFACES&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// force a JDK interface proxy even under a CGLIB default&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;OrderOperations&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Or suggest specific proxy interfaces:&lt;/span&gt;
&lt;span class="nd"&gt;@Proxyable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interfaces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OrderOperations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;OrderOperations&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AuditAware&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The rule that AOT now enforces
&lt;/h3&gt;

&lt;p&gt;Framework 7 formalises two long-standing recommendations: never register several beans inside a single &lt;code&gt;@Bean&lt;/code&gt; method, and always declare the &lt;em&gt;most concrete type&lt;/em&gt; as a &lt;code&gt;@Bean&lt;/code&gt; method's return type. These were always "best practice" — under AOT they become close to mandatory, because the build-time engine reads your declared return type to generate bean metadata. A method declared as returning an interface can leave AOT unable to see the concrete type.&lt;/p&gt;

&lt;h2&gt;
  
  
  AOT and GraalVM native image changes
&lt;/h2&gt;

&lt;p&gt;If you build native images — or you use Spring's AOT processing to speed up JVM startup — this is the section with the most teeth. Framework 7 switches to GraalVM's unified &lt;strong&gt;"exact reachability metadata"&lt;/strong&gt; format, and two changes will affect any code that contributes &lt;code&gt;RuntimeHints&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resource hints moved from regex to glob patterns
&lt;/h3&gt;

&lt;p&gt;The resource-hint syntax changed from &lt;code&gt;java.util.regex.Pattern&lt;/code&gt; to glob patterns, and the matching semantics changed with it. Previously &lt;code&gt;"/files/*.ext"&lt;/code&gt; matched both &lt;code&gt;/files/a.ext&lt;/code&gt; and &lt;code&gt;/files/folder/b.ext&lt;/code&gt;. Now &lt;code&gt;*&lt;/code&gt; matches a single path segment only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Spring 6 (regex) — matched nested folders too:&lt;/span&gt;
&lt;span class="n"&gt;hints&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;registerPattern&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/files/*.ext"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Spring 7 (glob) — to match nested paths you must be explicit:&lt;/span&gt;
&lt;span class="n"&gt;hints&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;registerPattern&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/files/**/*.ext"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Registration of "excludes" has been removed entirely. If your native build suddenly cannot find a resource it used to bundle, a too-narrow glob is the first place to look.&lt;/p&gt;

&lt;h3&gt;
  
  
  A reflection type hint now implies its members
&lt;/h3&gt;

&lt;p&gt;Registering a reflection hint for a type now automatically implies methods, constructors, and field introspection. As a result, &lt;code&gt;ExecutableMode.INTROSPECT&lt;/code&gt; and every &lt;code&gt;MemberCategory&lt;/code&gt; except the &lt;code&gt;INVOKE_*&lt;/code&gt; values are deprecated. Most hint code gets &lt;em&gt;shorter&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Spring 6:&lt;/span&gt;
&lt;span class="n"&gt;hints&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reflection&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;registerType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MyType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MemberCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DECLARED_FIELDS&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Spring 7 — the type hint alone is enough:&lt;/span&gt;
&lt;span class="n"&gt;hints&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reflection&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;registerType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MyType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pairing this with &lt;code&gt;BeanRegistrar&lt;/code&gt; (above) is the cleanest way to keep a 7.0 app native-image friendly. If you want background on why AOT and startup caching matter at all, I wrote up the JVM side in &lt;a href="https://ankurm.com/project-leyden-aot-cache-java/" rel="noopener noreferrer"&gt;Project Leyden: AOT compilation and smart caching&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Null safety: JSpecify replaces the Spring JSR 305 annotations
&lt;/h2&gt;

&lt;p&gt;Spring's own nullness annotations (&lt;code&gt;org.springframework.lang.Nullable&lt;/code&gt; / &lt;code&gt;@NonNull&lt;/code&gt;, which carried JSR 305 semantics) are deprecated in favour of &lt;a href="https://jspecify.dev/" rel="noopener noreferrer"&gt;JSpecify&lt;/a&gt;. The whole framework codebase has been re-annotated with JSpecify, which can specify nullness for generic types, arrays, and vararg elements — things JSR 305 could not express.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Old (deprecated):&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.lang.Nullable&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// New:&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.jspecify.annotations.Nullable&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Java code this is mostly a find-and-replace plus a dependency. &lt;strong&gt;Kotlin teams should pay close attention:&lt;/strong&gt; because Kotlin maps these annotations to platform vs. nullable types, the refined JSpecify nullness on Spring's APIs can turn previously-compiling Kotlin into a compile error (or vice versa). Expect to revisit some &lt;code&gt;!!&lt;/code&gt; and &lt;code&gt;?&lt;/code&gt; usages after the upgrade.&lt;/p&gt;

&lt;h2&gt;
  
  
  The HttpHeaders breaking change (web apps, read this)
&lt;/h2&gt;

&lt;p&gt;In 7.0, &lt;code&gt;HttpHeaders&lt;/code&gt; &lt;strong&gt;no longer implements &lt;code&gt;MultiValueMap&lt;/code&gt;&lt;/strong&gt;. HTTP headers are case-insensitive and behave more like a collection of pairs than a map, so the map contract was a poor fit. Several map-style methods were removed; a few fallbacks like &lt;code&gt;HttpHeaders#asMultiValueMap()&lt;/code&gt; exist but are immediately &lt;code&gt;@Deprecated&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Spring 6: treated headers as a Map&lt;/span&gt;
&lt;span class="nc"&gt;MultiValueMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// worked&lt;/span&gt;
&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Trace"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;traceId&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;        &lt;span class="c1"&gt;// map-style&lt;/span&gt;

&lt;span class="c1"&gt;// Spring 7: use the header-specific API&lt;/span&gt;
&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Trace"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;traceId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Trace"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;traceId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Trace"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// asMultiValueMap() exists but is @Deprecated — do not lean on it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have utility code, filters, or tests that cast &lt;code&gt;HttpHeaders&lt;/code&gt; to &lt;code&gt;MultiValueMap&lt;/code&gt;, those will not compile. Search for that cast specifically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing changes
&lt;/h2&gt;

&lt;p&gt;The TestContext framework got several useful upgrades, and one genuine breaking change.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JUnit 6 baseline.&lt;/strong&gt; Framework 7 targets JUnit Jupiter on the JUnit 6 platform, and JUnit 4 support in the TestContext framework (&lt;code&gt;SpringRunner&lt;/code&gt;, &lt;code&gt;SpringClassRule&lt;/code&gt;, &lt;code&gt;AbstractJUnit4SpringContextTests&lt;/code&gt;) is deprecated. If you are still on JUnit 4, migrate now — I have a prompt-driven approach in &lt;a href="https://ankurm.com/ai-prompts-optimize-update-junit-6-tests/" rel="noopener noreferrer"&gt;10 AI prompts to optimise and update JUnit 6 tests&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SpringExtension context scope changed (breaking).&lt;/strong&gt; &lt;code&gt;SpringExtension&lt;/code&gt; now uses a &lt;em&gt;test-method-scoped&lt;/em&gt; &lt;code&gt;ExtensionContext&lt;/code&gt;. This fixes DI consistency in &lt;code&gt;@Nested&lt;/code&gt; hierarchies, but a custom &lt;code&gt;TestExecutionListener&lt;/code&gt; overriding &lt;code&gt;prepareTestInstance&lt;/code&gt; may break — look up the current test class via &lt;code&gt;testContext.getTestInstance().getClass()&lt;/code&gt; rather than &lt;code&gt;testContext.getTestClass()&lt;/code&gt;. If &lt;code&gt;@Nested&lt;/code&gt; tests start failing, annotate the top-level class with &lt;code&gt;@SpringExtensionConfig(useTestClassScopedExtensionContext = true)&lt;/code&gt; or set &lt;code&gt;spring.test.extension.context.scope=test_class&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application context pausing.&lt;/strong&gt; Cached test contexts can now be &lt;em&gt;paused&lt;/em&gt; when idle and restarted on demand, so background threads do not run between tests. Control it with &lt;code&gt;spring.test.context.cache.pause&lt;/code&gt; (&lt;code&gt;always&lt;/code&gt; / &lt;code&gt;never&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bean overrides on non-singletons.&lt;/strong&gt; &lt;code&gt;@MockitoBean&lt;/code&gt;, &lt;code&gt;@MockitoSpyBean&lt;/code&gt;, and &lt;code&gt;@TestBean&lt;/code&gt; now work on prototype and custom-scoped beans.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New &lt;code&gt;RestTestClient&lt;/code&gt;.&lt;/strong&gt; A non-reactive sibling of &lt;code&gt;WebTestClient&lt;/code&gt; — the fluent assertion API without dragging in the reactive stack. Bind it to a live server, a single &lt;code&gt;@Controller&lt;/code&gt;, or the context.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Security implications
&lt;/h2&gt;

&lt;p&gt;Framework 7 does not &lt;em&gt;contain&lt;/em&gt; Spring Security — that ships separately as Spring Security 7 — but several Framework 7 changes directly affect how security behaves, which is exactly where silent issues hide.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Path matching alignment.&lt;/strong&gt; The deprecation of &lt;code&gt;PathMatcher&lt;/code&gt;/&lt;code&gt;AntPathMatcher&lt;/code&gt; and the &lt;code&gt;HandlerMappingIntrospector&lt;/code&gt; SPI changes how Spring MVC and Spring Security agree on which path a request maps to. If your security rules use Ant-style patterns, verify they still match the same URLs under &lt;code&gt;PathPattern&lt;/code&gt; — a mismatch here means a rule silently stops protecting an endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The javax removal touches security too.&lt;/strong&gt; Any security config or filter relying on &lt;code&gt;javax.annotation&lt;/code&gt; lifecycle hooks will silently skip initialisation, exactly as described earlier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CORS pre-flight behaviour changed.&lt;/strong&gt; As of 7.0, CORS pre-flight (&lt;code&gt;OPTIONS&lt;/code&gt;) requests are &lt;em&gt;no longer rejected&lt;/em&gt; when the CORS configuration is empty. Re-check that you are not relying on the old rejection as an implicit guard.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because "it compiles and starts" tells you almost nothing about whether your app is still protected, I treat the security layer as its own migration pass with its own tests. I walk through the Security 5→6→7 specifics — &lt;code&gt;SecurityFilterChain&lt;/code&gt;, the lambda DSL, the silent authorization changes — in the &lt;a href="https://ankurm.com/spring-security-5-to-6-to-7-migration-guide/" rel="noopener noreferrer"&gt;Spring Security 5 to 6 to 7 migration guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worth adopting while you are in there
&lt;/h2&gt;

&lt;p&gt;A migration is the cheapest time to pick up the new APIs, because you are already touching and re-testing the code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API versioning.&lt;/strong&gt; First-class support in MVC and WebFlux for mapping requests by API version, marking versions deprecated, and setting the version on the client side (&lt;code&gt;RestClient&lt;/code&gt;, &lt;code&gt;WebClient&lt;/code&gt;, HTTP interfaces) and in tests (&lt;code&gt;MockMvc&lt;/code&gt;, &lt;code&gt;WebTestClient&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Core resilience.&lt;/strong&gt; The old &lt;code&gt;spring-retry&lt;/code&gt; project is merged into &lt;code&gt;spring-core&lt;/code&gt; (&lt;code&gt;org.springframework.core.retry&lt;/code&gt;): &lt;code&gt;RetryTemplate&lt;/code&gt;, &lt;code&gt;RetryPolicy&lt;/code&gt;, plus &lt;code&gt;@Retryable&lt;/code&gt; and &lt;code&gt;@ConcurrencyLimit&lt;/code&gt; in &lt;code&gt;spring-context&lt;/code&gt;, enabled with &lt;code&gt;@EnableResilientMethods&lt;/code&gt;. &lt;code&gt;@Retryable&lt;/code&gt; even adapts automatically to reactive return types.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;JmsClient&lt;/code&gt;.&lt;/strong&gt; A fluent client for JMS in the spirit of &lt;code&gt;JdbcClient&lt;/code&gt; and &lt;code&gt;RestClient&lt;/code&gt;, and &lt;code&gt;JdbcClient&lt;/code&gt; itself gains statement-level settings (fetch size, max rows, query timeout).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP interface client groups.&lt;/strong&gt; &lt;code&gt;@ImportHttpServices&lt;/code&gt; turns a pile of &lt;code&gt;@HttpExchange&lt;/code&gt; interfaces into auto-registered client proxies grouped by host — covered in depth in &lt;a href="https://ankurm.com/spring-boot-4-http-service-clients/" rel="noopener noreferrer"&gt;Spring Boot 4 HTTP Service Clients&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The upgrade checklist (in the order I'd do it)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Get to &lt;strong&gt;Spring Framework 6.2 first&lt;/strong&gt; and clear every deprecation warning. Migrating from the latest 6.x is far easier than jumping from an older 6.0/6.1.&lt;/li&gt;
&lt;li&gt;Confirm your &lt;strong&gt;servlet container&lt;/strong&gt; supports Servlet 6.1 (Tomcat 11+, Jetty 12.1+). If you run &lt;strong&gt;Undertow, stop here&lt;/strong&gt; — it is not yet supported.&lt;/li&gt;
&lt;li&gt;Bump the &lt;strong&gt;Jakarta EE 11&lt;/strong&gt; dependencies: Servlet 6.1, JPA 3.2 (Hibernate ORM 7.1/7.2), Bean Validation 3.1.&lt;/li&gt;
&lt;li&gt;Grep for &lt;code&gt;javax.annotation&lt;/code&gt; and &lt;code&gt;javax.inject&lt;/code&gt; across the whole codebase and replace with &lt;code&gt;jakarta.*&lt;/code&gt;. This is the silent-failure risk — do it before you trust any test run.&lt;/li&gt;
&lt;li&gt;Fix hard compile errors from removed APIs: &lt;code&gt;ListenableFuture&lt;/code&gt; → &lt;code&gt;CompletableFuture&lt;/code&gt;, removed path-mapping options, &lt;code&gt;HttpHeaders&lt;/code&gt; map casts.&lt;/li&gt;
&lt;li&gt;Migrate &lt;strong&gt;nullness annotations&lt;/strong&gt; to JSpecify; Kotlin teams budget extra time for nullability fallout.&lt;/li&gt;
&lt;li&gt;If you use AOT / native images: update &lt;code&gt;RuntimeHints&lt;/code&gt; (glob resource patterns, simplified reflection hints) and consider moving programmatic registration to &lt;code&gt;BeanRegistrar&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Review &lt;strong&gt;proxy assumptions&lt;/strong&gt; (consistent CGLIB default) and add &lt;code&gt;@Proxyable&lt;/code&gt; where you need a specific proxy type.&lt;/li&gt;
&lt;li&gt;Run the test suite; fix &lt;code&gt;SpringExtension&lt;/code&gt; scope and &lt;code&gt;@Nested&lt;/code&gt; issues. Migrate any remaining JUnit 4 tests.&lt;/li&gt;
&lt;li&gt;Re-test the &lt;strong&gt;security layer end to end&lt;/strong&gt; — path-matching alignment and CORS pre-flight especially.&lt;/li&gt;
&lt;li&gt;Schedule the deprecation follow-ups with hard deadlines: &lt;strong&gt;Jackson 2.x&lt;/strong&gt; (removed in 7.2) and &lt;strong&gt;RestTemplate&lt;/strong&gt; (&lt;code&gt;@Deprecated&lt;/code&gt; in 7.1).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  An AI prompt to scan your project for Framework 7 blockers
&lt;/h2&gt;

&lt;p&gt;Paste this into Claude, ChatGPT, Gemini, or Cursor with your repository in context. It is tuned to the specific Framework 7 changes above, not generic "upgrade my Spring" advice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are auditing a Java/Kotlin codebase for a Spring Framework 6.2 -&amp;gt; 7.0 migration.
Scan the project and produce a prioritised report with file paths and line numbers.

Flag, in this order of severity:
1. SILENT FAILURES: any use of javax.annotation.* or javax.inject.* (e.g.
   @PostConstruct, @PreDestroy, @Resource, @Inject) — these compile but are
   ignored in Spring 7. List each and give the jakarta.* replacement.
2. HARD COMPILE ERRORS: ListenableFuture usage, HttpHeaders cast to
   MultiValueMap, removed path-mapping options (suffixPatternMatch,
   trailingSlashMatch, favorPathExtension), Undertow-specific config.
3. DEPRECATIONS WITH DEADLINES: Jackson 2.x (com.fasterxml.jackson) usage,
   RestTemplate usage, AntPathMatcher/PathMatcher in MVC or Security config,
   JUnit 4 + SpringRunner/SpringClassRule, &amp;lt;mvc:*&amp;gt; XML config.
4. NULL SAFETY: org.springframework.lang.Nullable/@NonNull to migrate to
   org.jspecify.annotations.*; for Kotlin, flag APIs whose nullability may shift.
5. AOT/NATIVE: RuntimeHints using regex resource patterns or MemberCategory
   values other than INVOKE_*; BeanDefinitionRegistryPostProcessor that could
   become a BeanRegistrar.
6. SECURITY RISK: Ant-style path patterns in security rules that may match
   different URLs under PathPattern; reliance on empty-CORS pre-flight rejection.

For each finding give: file:line, the risk, and the exact replacement code.
End with an ordered migration plan grouped by severity.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a broader set of Spring automation prompts, see &lt;a href="https://ankurm.com/ai-prompts-spring-boot-development/" rel="noopener noreferrer"&gt;10 AI prompts for Spring Boot development&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does Spring Framework 7 require Java 21 or 25?
&lt;/h3&gt;

&lt;p&gt;No. Framework 7.0 keeps a &lt;strong&gt;JDK 17 baseline&lt;/strong&gt;. JDK 25 (the current LTS) is &lt;em&gt;recommended&lt;/em&gt; and unlocks features like the Class-File API path, but 17 is the minimum. This is different from Spring Boot 4, which raises its own baseline — check the &lt;a href="https://ankurm.com/spring-boot-3-to-4-migration-guide/" rel="noopener noreferrer"&gt;Boot 3 to 4 guide&lt;/a&gt; for that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I migrate Spring Framework 7 and Spring Boot 4 separately?
&lt;/h3&gt;

&lt;p&gt;In practice they move together — Boot 4 pulls in Framework 7. But the failures have different root causes. Framework 7 owns the Jakarta cutover, bean lifecycle, AOT, and null-safety changes; Boot 4 owns autoconfiguration, properties, and starter changes. Knowing which layer a class belongs to tells you which guide to read.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the most dangerous change to miss?
&lt;/h3&gt;

&lt;p&gt;The removal of &lt;code&gt;javax.annotation&lt;/code&gt; / &lt;code&gt;javax.inject&lt;/code&gt; support. It fails silently: &lt;code&gt;@PostConstruct&lt;/code&gt; methods simply never run, with no error. Grep for those packages before trusting your tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is RestTemplate removed in Spring 7?
&lt;/h3&gt;

&lt;p&gt;Not yet. In 7.0 it is deprecated at the documentation level; it becomes &lt;code&gt;@Deprecated&lt;/code&gt; in code in 7.1. You can keep using it for now, but new code should use &lt;code&gt;RestClient&lt;/code&gt; or HTTP interface clients.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I still use Jackson 2 with Spring 7?
&lt;/h3&gt;

&lt;p&gt;Yes, for now — Spring 7 defaults to Jackson 3.x but falls back to 2.x. However, 2.x auto-detection is disabled in 7.1 and support is removed entirely in 7.2, so plan the move. See the &lt;a href="https://ankurm.com/jackson-3-migration-guide/" rel="noopener noreferrer"&gt;Jackson 3 migration guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Undertow work with Spring 7?
&lt;/h3&gt;

&lt;p&gt;No. Framework 7 requires Servlet 6.1, which Undertow does not yet support, so Spring removed its Undertow-specific classes. You must move to Tomcat 11+ or Jetty 12.1+, or wait for an Undertow release compatible with Servlet 6.1.&lt;/p&gt;

&lt;h2&gt;
  
  
  See also
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ankurm.com/spring-boot-3-to-4-migration-guide/" rel="noopener noreferrer"&gt;Spring Boot 3 to 4 Migration Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ankurm.com/spring-security-5-to-6-to-7-migration-guide/" rel="noopener noreferrer"&gt;Spring Security 5 to 6 to 7 Migration Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ankurm.com/hibernate-5-to-6-to-7-migration-guide/" rel="noopener noreferrer"&gt;Hibernate 5 to 6 to 7 Migration Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ankurm.com/jackson-3-migration-guide/" rel="noopener noreferrer"&gt;Jackson 2 to Jackson 3 Migration Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ankurm.com/spring-boot-4-http-service-clients/" rel="noopener noreferrer"&gt;Spring Boot 4 HTTP Service Clients&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ankurm.com/project-leyden-aot-cache-java/" rel="noopener noreferrer"&gt;Project Leyden: AOT Compilation and Smart Caching&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ankurm.com/java-25-lts-jeps-ai-prompts-migration/" rel="noopener noreferrer"&gt;Java 25 LTS: Every JEP That Matters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ankurm.com/ai-prompts-spring-boot-development/" rel="noopener noreferrer"&gt;10 AI Prompts for Spring Boot Development&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The Spring Framework 6→7 migration follows the same pattern I have seen on every major Spring upgrade: the compile errors are the easy part, and the silent behaviour changes are what reach production. The deleted &lt;code&gt;javax&lt;/code&gt; annotation support, the &lt;code&gt;HttpHeaders&lt;/code&gt; contract change, the consistent CGLIB defaulting, and the security path-matching alignment are the ones that pass your build and then surprise you at runtime. Work the checklist top to bottom, lean on the AI prompt to find the silent failures fast, and treat the security layer as its own pass. Do that, and Framework 7 — with its AOT-friendly bean registration, JSpecify null safety, and built-in resilience — is a genuinely better foundation than 6.2.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>java</category>
      <category>springboot</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
