Inside Spring Boot 4: Restructuring for the Future by Moritz Halbritter @ Spring I/O 2026
About this talk
In this session, Moritz Halbritter from the Spring Engineering team delves into the latest updates and migration strategies for Spring Boot 4.0. He discusses the removal of key features such as Undertow web server support and Pulsar Reactive, as well as the transition to a modular architecture aimed at improving maintainability and usability. The speaker emphasizes the significance of upgrading to smaller, modular components for better IDE performance and reduced dependency sizes. He also outlines the changes in auto-configuration, specifically how different technologies now require their own dedicated starters to streamline the development process. Furthermore, he explains the migration steps, guiding developers on how to successfully upgrade applications from Spring Boot 3.5 to 4.0 while addressing potential pitfalls and offering resources for further assistance.
Full transcript
[music] >> Yes, cool. Uh thank you very much for your interest in this talk. I never expected the the room so packed full crowded. My name is Moritz Halbritter. I work in the Spring Engineering team. I primarily work on Spring Boot and Spring Boot Starter IO. Um there are a lot of stuff on the slides. Um don't worry, you can download them afterwards. There is a QR
code at the end, so you don't have to take pictures or stuff like that. There is also very much text on the slides because you can use them as a reference when you do your upgrade yourself, okay? But the most important stuff is happening on your audio track, so you don't need to read it all the stuff. Um the first thing to know is um Spring Boot
3.5 is supported if it came out in 2025 and it's supported in 20 until like June of 2026, which is in like 2 months, okay? So you either buy enterprise support or you have to upgrade in in 2 months, otherwise you're falling out of the open source support. Not sure if you realize, but that's how it is. Okay, let's quickly talk about the requirements. Um Jürgen in
his keynote already iterated on them, so I'm going to skip over quickly. Um the baseline is still Java 17, so we haven't up the change line, but we strongly recommend Java 25. Um and we pull in those Spring Boot 4 pulls in Spring Framework 7 and Spring Framework 7 pulls in all all this stuff um like JUnit 6, GraalVM 25, and other But this has all been
covered in the keynote already. Okay. Um stuff we removed. Everyone likes removed features. Especially if you use them. Um Undertow support is gone. Not sure if you've noticed. Um we had to remove the Undertow web server. So we have like four supported web servers in Boot, Netty, Jetty, Tomcat, and Undertow, and Undertow is gone. Um the problem is Undertow has no Servlet 6.1 release yet. Okay? Um
we removed support for Undertow, and as it stands um we are not going to do to put it back. The migration path is use Tomcat or use Jetty. And this is from our issue tracker, so we had a conversation with one person who was not happy with the memory consumption of Jetty and Tomcat, and we fixed that, and now this person is happy, and I hope so
do you when you're using Undertow now and upgrade to um Tomcat or Jetty. Other removed features. We removed Pulsar Reactive. Not sure if you've ever heard of it. It it wasn't that popular. Okay, that's good. Um we also removed embedded executable Uber JAR launch scripts, which is a very cool name and a very wacky concept. So you essentially could execute directly on the shell your JAR file
without prepending Java-jar. Super weird feature. Not sure if anyone has used it. I'm I'm really glad that it's gone because it was a huge hack. It's gone now. We removed Spock. So if you use Spock for testing, um yeah, it's gone in 4.0. But it's back in 4.1, okay? So we had problems with with the Groovy update. It it didn't came in time, and in 4.1, Spock
is back. So don't shed tears. You can still test with Spock. We also transferred stuff out of the Spring team to other teams. For example, Spring Session Hazelcast is now maintained by the Hazelcast team itself, and Spring Session MongoDB is now maintained by the Spring MongoDB by by the MongoDB team. Okay, let's talk about the elephant in the room because this is the stuff which likely will
affect you the most. What we did is we hacked up the Spring Boot auto configure. So Spring Boot is essentially an embedded web server, something which binds configuration properties to Java classes, and then a lot of auto configuration. And auto configuration works by looking at your class path, seeing what's on there, and then it configures the Spring context. This is the magic behind Spring Boot. So when
you put something on the class path, stuff starts configuring yourself, and you are happy because you don't have to write boilerplate. Every auto configuration we had for all the technologies, Kafka [snorts] and Flyway and Redis and everything, was inside this JAR, okay? This was a 2.1 MB JAR with all the auto configurations. Also all the auto configurations in it. What we did is we split it up.
We extracted several smaller modules, okay? So there is now one for Spring Boot actuator. There's now one for WebMVC and so on and so on. So every technology now has their own small module. And um by doing that, we also um realized that we have to split up Spring Boot test auto configure because this whole auto configure thingy is also mirrored for tests. Like there's WebMVC tests
and stuff. Why? Why would you do that, Moritz? Why would you force that on us? Um the first thing is it enables some cool features. For example, better signal for auto configuration. So in the past, if you want to use a web client, you had to put WebFlux on the class path, right? Spring WebFlux. Spring Boot has no way to know if um if you put WebFlux
on the class path because you want to essentially have at control of the port in a reactive web server or if you just want to web client. So what we did is we configured reactive web server support, which may not be what you want. Now, you could put the web client module on the class path, you only get the web client, and if you want to have
like real WebFlux support for reactive controllers, you put the WebFlux module on the class path. So we now have a much better signal what's actually on the class path and what we should configure. It also enables new features. For example, if you think about metrics and tracing, they have been coupled to the Spring Boot actuator. So if you wanted metrics or if you wanted tracing, you always
had to get actuator on the class path because the auto configuration for metrics and tracing was in Spring Boot actuator auto configure, okay? Now it has a separate module. So now you can do metrics or tracing without actuator, and sometimes interesting. It also gives you a much better develop experience because um if you auto complete in in your IDE with control space, then you get all kinds
of classes because your IDE has indexed this big 2 MB Spring auto configure module, and there is all kinds of stuff in it. And then you have like a Kafka customizer and some other customizer stuff, which you don't care about because you maybe don't use Kafka, okay? Um now you have like less classes in the auto completion because you only have this stuff on the class path
which you're actually using. The same for properties. If you hit like control space in a 3.5 application, IntelliJ or Eclipse or whatever will suggest you like all the properties which are there in Spring Boot, even if they have like no relation to your application because maybe you have properties for Redis in there. You you may do not have Redis on your application. Why is there a property
for Redis if I don't use it, right? Now there are only the properties inside the auto completion which you're actually using because the IDE now indexes smaller modules, and there is only stuff use. And there are also slightly smaller JARs for you because instead of this big auto configure thingy which is already always on the class path, you now have like smaller modules on the class path,
okay? So and your JARs will get smaller because you only include what you need. Let's look at Spring Boot 3.5. This is like 65 MB total, and here I filtered on the Spring Boot auto configure. Here's the Spring Boot auto configure thingy, 2 MB, and there is the Spring Boot core thingy, then there's another auto configure module, actuator, and some other shamble tools. When you upgrade to
4.0, something it looks like this. There are a lot of smaller Spring Boot modules. So the Spring Boot core is still there. It's a bit smaller, but here is the auto configure. Previously 2 MB, now 3 371 KB, but there are a lot of more of them. But if you sum them up, this here is smaller than this here, and this is still smaller in 3.5 because
the other dependencies we pull in like Hibernate and other stuff just got bigger. Okay? So we saved you some time, but please complain to the other projects that they polluted your stuff. We also did it for ourselves, okay? We we just did it to for for the stuff I already mentioned, but we also did it to improve the maintainability of the Spring Boot code base. It all
started out by um one of our team members had the idea of why do we have like everything in one module? It's it's a god module. There everything is in there. Why shouldn't we split it up? And we had that idea for a long time, and every time we said, "Yeah, no, no, it's too much work. It's too much work." It was a lot of work, but
there are benefits of it because now the the the the the code base for ourselves [snorts] is more maintainable because we now have more isolated changes. So for example, if I fix a bug in Redis, we only have to touch the Redis module and not rummage around in the big module. Um we also have the architecture validated through the build system because we now have smaller modules
on those modules may depend on each other or not, but now you can't accidentally, for example, put a reference from Kafka to some Redis class because it's in a totally different module which have no dependencies on on the Gradle build system. Of course, we had like ArchUnit rules to validate our architecture, but sometimes stuff falls through the cracks, and now it's much safer because like essentially Gradle
protects us to do um architecture violating stuff. And we also get a much much faster build because um so we use Gradle as a build system, and Gradle is like really smart when to execute stuff and when not to execute stuff. So before that, um if you touch one class, [snorts] it doesn't matter which one in Spring Boot auto configure, it has to run all the tests
inside the Spring Boot auto configure module, and there are a lot of them, and then it takes like 7 minutes and something, okay? If you do the same thing in one of the modules of Spring Boot 4, your build runs in 30 seconds, okay? Because Gradle is now Here it has to execute like 1,800 tasks. And here it only executes 400, and this stuff here is cached,
So our build just got much faster if we only do changes in one module. That's no benefit for you, but it's a huge benefit for benefit for us. We also improved the starters arrangement because usually if you use Spring Boot, you don't care about the modules. You care about the starters because that's the stuff you actually use. If you go to start.spring.io, it puts starters in your
dependencies and not modules. And those starters transitively depend on the modules. What we did is every technology now has their own starter, okay? Before that, there were some starters in which some technologies which had no starter. Now every starter has a technology. It's always named Spring Boot Starter and the name of the The package is always org.springframework.boot.technology. This will get interesting when you do the migration because
now if you get a compiler error that some class is missing and you know the package, you know in which starter it is. Every starter also has a test starter, okay? And test starters are always called Spring Boot Starter Technology Test. If you find something which doesn't match the pattern, please create a bug issue bug tracker issue because it's a bug. The package is always called org.springframework.boot.technology.test
and it contains useful code for testing. For example, @WebMvcTest is in the Spring Boot Starter WebMvcTest module, That's the That's the idea. We We made everything like the same. Even if the starter is empty. So if we have some starters which have no test dedicated test support, there is still a test starter. This test starter does nothing. It only depends on Spring Boot Starter transitively, but [clears
throat] we wanted to have the It should all be the same. It's It's always the same pattern. You add the starter, you add the test starter. Even if the test starter is empty, we don't care. We created the test starter which is empty, just add it, okay? Maybe we put something in there in the future, just add it. We want to have always the same structure. And
you can remove your dependency to Spring Boot Starter Test because every test starter now depends transitively on Spring Boot Starter Test. We see that in the migration. Some examples. WebMvc. The starter is called Starter WebMvc and the starter for test is for WebMvcTest is called WebMvcTest, very creative. The WebFlux thingy is called WebFlux WebFluxTest. Guess what the security thingy is called. Security and Security Test, surprise surprise.
Okay, so it all follows like the same pattern, okay? The technology name, then there is a starter for the technology and a starter for the test. All technologies now have a starter, okay? Some dependencies didn't have a starter in 3.5. For example, Liquibase, Flyway, Kafka and so on and so on. Um how those worked is essentially you put like Flyway Core itself on the classpath and then
auto configure detected it and auto configured itself, but now there is a starter. There is now a starter Flyway, a starter Kafka and whatever. So every technology has a The full list can be found in the migration guide. start.spring.io is aware of that. So if you create a Spring Boot 4 project and put like Kafka or Flyway or whatever in the dependencies, it will put like the
Kafka starter in Um if you are creating a Spring Boot library, please do not try to support Spring Boot 3 and Spring Boot 4 in the same artifact. It will be a lot of pain because we moved a lot of stuff. And then you have to do a lot of reflection hacks to get it done. Please don't do it. If you have support for Spring Boot 3,
create a Spring Boot 3 support version of your library. If you support 3 4, please create a Spring Boot 4 library. We moved some internal classes. For example, Bootstrap Registry is moved, the environment post processor is moved and all all the other stuff. Okay? And if you update, please don't forget Spring Factory Keys. If you don't know what Spring Factories are, uh good for you, but library
authors will know and um because when we upgrade our libraries, it's easy to forget those because they are essentially string keys and then stuff will not get found and stuff crashes and Uh here. Spring Factories. Okay. Migration guide. How to do your actual migration. So this was all the motivation for that stuff, how to actually migrate your application. Okay. The first thing you do is you add
Spring Boot Starter Classic and Spring Boot Starter Test Classic. Those modules depend transitively on all the other modules we have, okay? This is essentially our migration path to the smaller modules. So we add them first because they depend on everything and then we are sure you have everything on your classpath. Then you get your stuff to compile again by fixing the imports because we changed all the
package names, right? But we only changed the package names and not the class names. So you can always find it by getting your IDE to find the class and then you just fix the import. Okay. Now everything is compiling again and your application starts up. Use the Spring Boot Properties Migrator to fix the properties. The thing is not new, um but use it. It will tell you
there is an old property, upgrade to the new one. Then you're going to remove the Spring Starter Test Classic because what you actually want is you want the smaller modules and not the one which depends on everything because this doesn't solve the problem with the smaller jars, okay? Now stuff starts to fail to compile. And now you add technology specific starters and test starters and the cool
thing is now when the compilation fails, it will tell you I can't find this class in this package and because the package has a direct relation to the starter, you know which starter to include, okay? Look at the imports if lost. So if it doesn't find for example something in the Rest Client package, you add Spring Boot Starter Rest Client. If it doesn't find something in the
Security package, you add Spring Boot Starter Security, right? That's That's the pattern. Um some other stuff which have tripped up people. The Spring Boot Starter AOP is now called Spring Boot Starter AspectJ because the technology is actually named AspectJ and not AOP. So we renamed this one. And this one will hit you too. Spring Boot Starter Web is now called Spring Boot Starter WebMvc. The Spring Boot
Starter Web is still there, but we're going to remove it in the future because the technology is not called Spring Boot Web, it's called Spring Boot WebMvc and we want consistency, so this thing is called Spring Boot Starter WebMvc, okay? And the test starter is called Test Test. Um thing to look out. Rest Template is deprecated for removal, okay? So we deprecated that in the past because
we didn't want to add new Now we have deprecated it for removal, so it will be removed in the future. If you have Rest Template in your application, upgrade to Rest Client now. Spring Boot Starter WebMvc now only pulls in the stuff which you need to have WebMvc on the server. It does not pull in any auto configurations for clients, so if you do outbound calls, there
is no auto configuration for it anymore and stuff starts breaking. And how to get it back? If you want to have the Rest Client, guess what you'll add? Spring Boot Starter Rest Client. And if you need the Reactive Web Client, what you'll add is Spring Boot Starter Web Client, okay? Just be prepared that you will have to add some starter to get your client arrangement working again.
Okay. Let's look at a real world example. How do we migrate the Pet Clinic from 3.5 to 4.0? There are different ways to migrate. For our enterprise customers, there is Spring Application Advisor which does that stuff. It uses custom OpenRewrite recipes under the hood. It has seamless CI/CD integration, so you can like upgrade a lot of projects like at once and it's built by Tanzu, the the
the team essentially behind Spring. Then there are also OpenRewrite recipes. There are I think there are commercial ones from from OpenRewrite themselves, but there are also community recipes which you can use. You can also use your favorite LLM. Give it a try. Maybe it works, maybe it doesn't and who knows? That's the fun with the non-predictability stuff. There's also an MCP server coming for Spring Application Advisor,
so if you're using enterprise stuff, then you can point your LLM to this thingy MCP and then it does magic and then everything works, so I heard. Rachel talked about it in in her talk. Or you do it by hand and this is what I'm going to show you because I want you to actually learn how stuff works and not like how to prompt the LLM until
it works until it works. Okay. So what we're going to do is we are first going to upgrade the Spring Boot version, okay? It's 3.5 and we are going to upgrade it to 4.0 and then we run the and then stuff starts breaking. And it complains about I could not find org.testcontainers.mysql. Oh god, what is that? this is the old dependency and this is Testcontainers 1 and
Spring Boot 4 pulls in Testcontainers 2 and they took the opportunity because every library We really love major upgrades because we can break all the stuff, which is great. Um they essentially upgraded to the new module just new artifact names, okay? This is the only thing which you do, just um pull in your Testcontainers in front of it and it compiles again. Then we add the Starter
Classic and the Starter Test Classic. So we had this here, the Spring Boot Starter Test. This is no longer necessary, replace it with Spring Boot Starter Test Classic. Now it pulls in all kinds of stuff on your Now you get it to compile again and now you will see how we moved classes, okay? Stuff doesn't compile, the web package here is missing. What What's happening? We have
to fix the import. It's now this because it's the WebMvc package, it's called org.springframework.boot.webmvc and now fix all the imports and a lot of imports later, the stuff is running Then it complains there is a test rest template missing. What's that? Moritz, you have told me if I put on the starter test classic and stuff on the class path, everything is working. What's happening? And this is
a bug. We have fixed it. I found that while working on the presentation. Fun, fun, fun, but it's now fixed in the next version. Um just for reference, you don't need that when you when you upgrade to 4.06, which will came out which will come out next week, then you're not affected by this bug. I was affected just to warn you. If you're trying it right now,
you will be affected by the same bug. In 4.06, it's fixed. You won't be affected anymore. But just for reference, the test rest test template is in spring boot starter web MVC test, and you can find that out by looking at the full package, which is not in in the error message, but if you look at the full error message which you get from your build tool,
it's obvious that the thing is in spring boot starter web MVC test. Therefore, we added to get it to compile Now, um our application is running now. The tests are still failing, but our application is able to start up. Okay? So, we do our boot run. We add the spring boot properties migrator, and we do a greater boot run again, and we watch for log messages because
this properties migrator essentially puts out warning log messages if it finds any properties which are deprecated. And it tells you what is the new one. So, you do that. You watch for log messages. In our case, there are no log [snorts] messages, so we can remove this thing again because we only needed for the migration. Throw it away again, and be happy. Now, run the tests. The
tests fail because there is a test rest template missing, but this time not on the compiler level, but on the spring context level. So, what's going on there? Spring boot doesn't auto configure test rest templates and test rest clients and stuff out of the box anymore. Okay? This is behind a new annotation which is called auto configure test rest template, which is in the spring web MVC
test, I think. And just put that on your test, and the bean is there again. Now, our test fails with some other weird error, which has nothing to do with spring, but it's some some other other other thing. How to fix that? Okay, so the property migrator only looks at the properties inside your application properties or application YAML file, but not at the ones which you specify
inside the spring boot test annotation. But if you use IntelliJ, for example, you get like those squiggly red lines, which usually indicate there is a problem. Okay? >> [clears throat] >> And there is also a problem because it's like strike through, which means it's What you can do is you can hit alt enter on this thingy, and then click on the three dots here, and then run
the same inspection on the whole code base, and IntelliJ will find all the deprecated properties which you have used in test, and then you can fix them. And after we fix them, so what happened server.error.include-message is not there anymore. It's now spring.web.error.include-message. And if you replace all the properties in the tests, and this time it was only one, the tests are green, too. Now, the tests pass,
and we can run the That's cool. Now, we have to clean up because what we actually want is we want the smaller modules, not not the one which pulls in everything. We remove spring boot starter classic and spring boot starter test classic. And then stuff starts failing again because you can't compile because stuff is missing from the class path. But now, you see that this here gives
you a cool error message because org.springframework.boot.data.jpa.test.autoconfigure does not exist. So, what you add on the class path is spring boot starter data JPA test. And you do that for all the stuff. Either you do it by incrementally compiling and fix and and looking at the at the statements and fixing it, or what you can also do is you have a lot of starters already. Just add the
test starter for every starter you have. Right? You have web MVC, you add web MVC test. You have data JPA, you add data JPA test. You have security, you add security test. And then usually, stuff is fixed. Now, the rest template builder is missing after we added everything. And this is actually in production code. Okay? So, this is what I said, um there is no auto configuration
anymore for the clients. So, if you use a client, look at this package here, and then add spring boot starter rest client. Okay? Here. Spring boot starter rest client. Always let the imports guide you, and you will find the starter you will need. And after you've done that all, yeah, don't forget to add the test rest test module. Okay? So, every starter has a test module. Just
add it. Then your build is successful. All the tests pass. Your application is upgraded. Now, you have to replace the deprecated starters. You take the spring boot starter web, replace it with spring boot starter web MVC. You're done. That's essentially it. Okay? Add the classic modules, get it to compile again, fix your tests, remove the classic thingy, put in the smaller ones, let you guide by the
imports, stuff is done. This is now upgraded on 405. [snorts] All the tests pass, and you have like the small modules on the class path, so your application is actually using the smaller jars and not not the big ones. Cool. So, this was the story how to upgrade But there are other subtle bugs below the waters with which you can trip you up. We upgraded to Jackson
3, and Jackson 3 the next major of the Jackson framework which does all the JSON stuff inside your application usually um since I don't know, 15 years or so. I think Jürgen mentioned it in the keynote how how how long ago Jackson 2 was released. And Jackson 3, of course, because it's a new major version, changes stuff. spring boot 4 and spring boot spring framework 7, we
use Jackson 3. We introduced support for it. We are going to deprecate Jackson 2. Okay? So, Jackson 2 will be going away. we also auto configure by default Jackson 3. So, if you create a a spring boot 4 application, you get Jackson 3. Okay? You have you can get Jackson 2 back for a limited amount of time, but we really really recommend Jackson 3. So, what what
did they do in Jackson 3? They renamed all the packages, like we did. You see the pattern? >> [snorts] >> Framework and library authors uh love this trick. Um it's now all in tools.jackson. Okay? But the annotations aren't. So, for example, at um J at JSON name at JSON property and JSON creator, all that stuff is still in the old package. Okay? They tried to move it
to the new package, but that was too much, so they changed it back to the old package. Okay? But everything else is under tools.jackson and not the com.fasterxml.jackson They also removed the object mapper. Okay? The object mapper before was the thing which you interact when you want to essentially marshal or demarshal your object into JSON or from JSON. And this was always mutable. So, consumers of this
object mapper could fiddle around, and then this stuff started changing globally JSON defaults, which is not that good. Now, they have the immutable JSON mapper. Okay? So, it's now called JSON mapper and not object mapper anymore. They also removed checked exceptions, so now they are have are using [snorts] unchecked exceptions everywhere, so you don't have to catch the JSON processing exception, and then I don't know, wrap
it in a runtime exception or whatever you do with that. And they updated default settings, and that's the fun part. Because um the problem with with this stuff is usually you you use it to create JSON out of your DTOs. How many of you write tests to actually assert on the JSON and not on the um I call the controller, and I let spring MVC demarshal it
um deserialize it into an object, and then assert on the object which is returned. So, you never see the JSON, right? So, the JSON can subtly change, and you don't know it. And that's the problem with those upgrades with Jackson 2 to Jackson 3. There could be subtle changes in the API, and you don't know it because you usually don't assert on the on the plain text
JSON stuff, but on like the deserialized objects. yeah. So, and this is API breaking stuff if you change the if you if you change if if Jackson changes something in your JSON essentially. Okay, so the updated default settings sort properties alphabetically is not true. Write dates as timestamps is false, so you get like ISO stuff inside strings, and no longer this Unix epoch timestamps thingy which no
one can read. And fail on trailing tokens is true by default. It also does the auto detection of modules. So, Jackson has those modules, for example, for Kotlin and JSON parameters, date time stuff, and and this is all now auto um auto discovered. Um you can control that if you don't want that with spring.jackson.findandaddmodules. This is the spring boot property to opt And more in the Jackson
3 migration guide. So, they've written an extensive migration guide. If you really want to know what everything has changed, look at the Jackson 3 migration guide linked How to use Jackson 3 in spring boot. Instead of an object mapper, you now inject the JSON mapper. Okay? And then you can create JSON out of it. Instead of the object mapper customizer, you use a JSON mapper builder customizer
because a JSON mapper is now immutable. You have to use this builder thingy. Okay, you get the builder, you modify the builder, and then um Spring Boot will create the immutable JSON mapper instance and inject it everywhere. Some properties have moved. So, this is old one. Uh that is the new one, sorry. Those are the old properties. Um didn't have JSON in there. Okay, if you don't
do want to use Jackson 3, but you want to stay on Jackson 2, which I absolutely do not recommend, but you can do it. There is spring.jackson.use-jackson-2-defaults property. And this property, if you set this to true, Spring Boot will do its best to configure the JSON mapper in a way that it behaves like the object mapper in Jackson 2. Okay? So, it's compatibility mode. But um we
can't promise that it covers everything. So, Um it works without Jackson 2. Okay? So, you get Jackson 2 behavior with Jackson If you want If you really want to have Jackson 2, there's dependency management for Jackson 2 still there. Um you can like just depend on the the um com.fasterxml.jackson.dataformat.xml, Jackson database, for example. We still manage the version for you. And you can also use the object
mapper alongside the new JSON mapper. So, this is the Jackson 2, this is Jackson 3. You can mix them because they have different packages. All good. auto configuration, there is a module, and this thing doesn't have a starter because we don't want to like steer people to it, but if you really want to do it, you can do it. There is a Spring Boot Jackson 2 module,
and there is the auto configuration for Jackson 2 inside But this stuff is deprecated, right? So, we released a new module and deprecated it right away, which we normally don't do, but here it is. It's bold, and note is like written in uppercase letters because this stuff is going to go away, right? You have been warned. This configures an object mapper bean like Spring Boot 3.5 had,
and then you can use it. You can use spring.jackson-2-properties to configure all that stuff. You can use the old Jackson 2 object mapper builder customizer for customization. But um other stuff inside the Spring portfolio might still use Jackson 3. For example, if you use Spring Web MVC, it uses Jackson 3 by default. And you now have to opt in so that Spring Web MVC uses Jackson 2.
And there are a lot of properties for Web MVC, WebFlux, GraphQL, RSocket, and WebSocket so that you can switch them to Jackson 2. But this is not possible for all Spring portfolio projects. For example, Spring Data or Spring HATEOAS, and I think Spring Data REST or whatever the thing is called. This I think they are hardcoded to use Jackson 3. So, it's not possible for all the
technologies. But there is a way to maybe ease the upgrade if you want to if you need more time to upgrade from Jackson 2 to Jackson 3. Everyone's steamrolled already? There are like 100 slides more to go. Okay, let's talk about Spring Retry. There was a talk from Jürgen about that already, resilience features in Spring Framework, so I'm going to just show it quickly. The Spring Retry
project is in maintenance mode only because all the features from Spring Retry have been integrated into Spring Framework itself. So, um instead of using Spring Retry, remove it, migrate to the Spring Framework thingy. You You You essentially put on a configuration class at enable resilient methods, which opts in into this proxy stuff for retrying, and then you can annotate your methods like with at retriable. And here
you can see, for example, timeout 10 seconds, delay string 2 seconds, and some random jitter. And this stuff here, you can also use the Spring Expression Language to reference properties. Yeah. Then there's JSpecify. Um Sebastian did a talk yesterday about all the null safety stuff. What is JSpecify doing? So, Tony Hoare has invented the the null reference. He called it the billion-dollar mistake, and um JSpecify is
trying to fix it. Um Spring Framework had already nullable annotations. This was the org.springframework.lang.Nullable. This is now deprecated. JSpecify is an industry collaboration to create another standard for nullness. Um there are a lot of big players in the industry, for example, Google, JetBrains, Meta, Microsoft, Oracle, um Sonar, Square, Uber, and there's also Broadcom here here. So, thank you very much, Sebastian, not sure if you're here, um
for your work, what you've done on the JSpecify specification. And I know exactly what you're thinking. Why is there a 15th standard for nullness in Java? Um the thing is the other standards were not that good, and now there's a 15th good one. Okay, so the main work is actually in the JSpecify specification. The JSpecify specification specifies how those nullable annotations should behave. If you look at
the JSpecify JAR, there are like four annotations in it, and that's it. It's four annotations. But the main work which which which those people have been done on the working group is essentially how to specify how those behave so that tool vendors can now implement the specification, and the stuff behaves the same everywhere. The new annotations are type use. Like you can now also annotate generic stuff.
So, this list of nullable list of string denotes a list which is not nullable itself, but it can store strings or null. Okay? Four annotations, nullable, non-null, null-marked, and null-unmarked. Those are the four annotations. And now what we did is we hadn't at none at non-nullable. So, if you look at the Spring Boot code base, we never had nullable nowhere. Like the Kotlin people were like upset
about that because if you interact with Spring Boot 3.5 with from Kotlin, you always get platform type stuff can be null, stuff cannot be null, have fun with the null pointer exceptions. Spring Boot now has full JSpecify support. Okay? So, this is the patch um we did. It's only 25,000 lines. So, looks good to me. Um this patch file is actually like 6.8 megabytes in size or
something like that. So, um we touched a lot of the code base. We went through all the stuff. We have null We have null away, which does compile time checking of that stuff. So, there This is now like really checked with the with the type system. And we annotated like every API we have, and every internal stuff, and every And now [clears throat] we have like 9,000
something nullable annotations in the code base. Okay, those are the JSpecify nullable annotations. Kotlin automatically understands JSpecify, but if you use Java, this is also cool because take a look at this thing here. It's um a Spring application run listener. And what what you what we try to do is we we hook into the fail thingy, and we want to log essentially, "Oh no, the name of
the application failed to start." And and and a smiley, which is just sad. Okay? Can you Can you spot the problem? There is a problem here. This will blow up in some cases because the problem is, if you read the Javadoc, it says this context here, it actually can be null if a failure occurred before the context has been actually created. And then it puts null here.
You You call get environment on null, and everything blows up, and your failure listener is now creating a new exception instead of logging, which is bad. If you upgrade to Spring Boot 4, you can see there is now an at nullable on it. So, it's now annotated with nullable, so you know that it's null, and stuff which is not annotated is never null. And now IntelliJ warns
you that if [snorts] you invoke get environment on this stuff, it may produce a null pointer exception. Maybe you should do something about that. And the same for Kotlin because we have nullable on there, the type which you get here is no longer a platform type which can be null or cannot be null. It's guaranteed to be nullable. So, there is this question mark, and then you
can't do context.environment because this here can't be executed. This is not null safe, so you need to have the the Kotlin Elvis operator or null safe call operator, whatever the thing is called. Okay? So, now Kotlin forces you on compile time to deal with the null pointer exception. And you can have the same with Java, but you have to add on your another um plugin. If you're
interested in that, I recommend watching um Sebastian's talk about JSpecify where he went into that in detail. And now we have null safety portfolio wide. Okay, Spring Boot, Spring Framework, Spring Data, all the other Spring Spring Boot project All the other Spring projects, they are now all annotated with at null, so you know if there's no at nullable on it, it it can't be null. If there's
at nullable on it can be null, and you should deal with Okay, new features. HTTP service client. Not sure if you know that one. Maybe you know OpenFeign. Um this is essentially you write an interface, and Spring does behind the covers create a client for you. So, you annotate it with at get exchange. You can use um variables here. Then you do import HTTP service clients. Um
you You You point it to the interface which you just created. You give the group a name. The group is used for properties, and then you configure it. For example, you can set the timeout, you can set the read timeouts, and you set the base URL. And then you can inject this thingy here. Okay? This is the interface which has no implementation written by you, but Spring
Framework will create at runtime a thing which does HTTP calls behind the covers. Okay? It's really cool because now you have like easy way to call other services. And the cool thing is, because this is an interface, you can super easily mock it inside your unit You don't You don't have to fiddle around with mocking rest template builders and stuff. You can just directly mock unit. We
also have API versioning. This is also very cool. So, you have a controller. This is the 1.0 version of it. This is the 2.0 version of it. This This is the 3.0 version 0.8. And if you look here, there is a plus. So, it's 3.0 3.0 or better. And now callers can specify which version of the API they actually want to invoke, which is great if you
have like strong backward compatibility for your API. You can do it now with built-in Spring features. Here, then you have properties inside boot. So, you can say this is the default version is 1.0. So, if the client doesn't say which version um it want to have, we always invoke the version 1.0. Um we're going to use the X-Version header. You can also do query parameters and other
stuff. And then we say, "Okay, version 1, 2, 3, and 4 is supported." And if the client sends some different version, it just gets a 400 bad request. And we can see that here. So, if I invoke it without a version, it works 1.0. 1.0 explicitly, it works it works 2.0, and it works 3.0. It invokes 4 is mapped to 3 because it's 3.1 plus. And if
you try 5, it fails with a 400 because we specify here that 5 is no is no valid version. Right? This is API versioning, which is now um supported inside Spring Boot via properties. Then we have the rest test client. The rest test client is um it has been in Spring Framework since I I think last version, but we never had auto configuration in boot for it.
So, if you wanted to use the rest test client, you always had to write some boilerplate code to essentially get the rest test But now we have the @AutoConfigureRestTestClient, and then you can inject it inside your test method. And then you can use rest test client.get pointed to something. This is automatically configured to point to your web server, which is started by Spring Boot test. And then
you can do expect body is equals to with a much better API than the than the old rest test template had. It's also has an AssertJ matcher, which is like really cool. So, it's the same, but here you essentially get the response back, get the response, and then you have this assert that, and this plugs into the AssertJ infrastructure. And now you have like this cool fluent
API. Assert that response has status okay, the body text is equal to something, which is I think a very nice pattern to actually test your rest controllers and stuff like that. So, um don't use test rest template or other stuff anymore. I would um recommend to migrate to the rest test client because it has a very nice API to actually assert on and stuff. Okay. Some other
miscellaneous changes, grab bag of stuff. The liveness and readiness probes are now enabled by default. Before that, they were automatically enabled if, for example, [clears throat] Spring Boot detects that it's running in a Kubernetes environment, but now they are enabled by default like everywhere. MockBean and SpyBean are gone. They have been already deprecated since 3.4, but now they are removed. Um use MockitoBean or MockitoSpyBean instead. And
it looks like that. You essentially put @MockitoBean on on a field. It um Spring Framework behind the scenes creates a mock for mock for it, put it in the context with a higher priority than your existing my service, and then the mock is injected everywhere. Then you can here program this mock, and then here you can use the mock or use any component which got the mock
injected, and then you have nice unit test. Optional Maven dependencies are no longer included in the uber jar. So, if you like optional dependencies and you want them in your uber jar, you have to opt in. You configure include optional on the Spring Boot Maven plugin. If you don't do that, we just leave them out. So, for example, I think Lombok is declared as optional by default
when you create it from start.spring.io. So, Lombok is no longer in the current stuff like that. And smaller changes. The Mockito test execution listener is gone. We deprecated it, but a lot of people missed that because it's it's it's hard to detect if you want to use like @Mock fields and stuff like that. Please use Mockito extension from Mockito itself with JUnit 5 and stuff. This This
is essentially Yes. So, the those were my like 500 slides of the Spring Boot test Um scan this code if you want to download the slides. Very good. I [snorts] see a lot of mobile phones. And then you can use the slides for reference. So, I um I'm sorry that I put that much text on the slides. Normally, I don't use that, but uh normally, I don't
do that, but for reference, just now you have it if you want to migrate. Everyone scan the thing already? If not, come to me afterwards. Um so, wait wait wait, there's more. There was like 500 slides more, okay? Literature for all the stuff. Like, we have a migration guide. We have release notes. Jackson has a migration guide. We have a blog post series about other stuff because
this wasn't everything which we did because this like covers Spring Boot and not all of it because what I've not covered is this and this and this. Because I only have like 50 minutes. I Maybe I should have submitted an 8-hour workshop, but um that's how it is. So, there is there is more stuff in the migration guide. So, please read that migration guide and go through
it. But I think the modularization changes which we did is the most difficult ones to navigate, and I hope I gave you some guidance about how to navigate that. thank you very much. >> [applause]
More from this event
See all 38 talks →
Spring I/O 2026 Keynote
1:08:44
The Spring AI Ecosystem in 2026: From Foundations to Agents @ Spring I/O 2026
43:39
Breaching LLM-Powered Applications: Overcoming Security and Privacy Challenges by Brian Vermeer
48:40
New in Spring Security 7: MFA, OAuth2 and more by Daniel Garnier @ Spring I/O 2026
46:43