Spring Boot 3 was released at the end of 2022 but some projects are still using Spring Boot 2.7. I’ve recently migrated several projects and libraries from Spring Boot 2.7.15 to Spring Boot 3.2.4 and I’d like to share some insights into the changes I had to make to ensure everything functioned smoothly.
Just a heads up, the update required quite a bit of file changes. The majority is due to the transition from Java EE to Jakarta EE, resulting in many javax.
imports being replaced with jakarta.
imports.
While I aimed to provide as thorough an overview as possible, it’s important to note that your service might encounter specific situations not addressed here. This is because the implementation discussed pertains to a specific approach utilized by the projects that I have worked with.
Dependency changes
The first thing is to change the dependencies. Let’s start with the spring dependencies. The examples will be shown using gradle groovy configuration.
Upgrading Spring
Look for the usage of the plugin id org.springframework.boot
and set the version as 3.2.4.
this
plugins {
id 'org.springframework.boot' version '2.7.11'
becomes
plugins {
id 'org.springframework.boot' version '3.2.5'
We also need to update the Spring Cloud org.springframework.cloud:spring-cloud-dependencies
from version 2021.x.x to 2023.0.1
Before we had something like this:
implementation platform("org.springframework.cloud:spring-cloud-dependencies:2021.0.9")
and now we have something like this:
implementation platform("org.springframework.cloud:spring-cloud-dependencies:2023.0.1")
make sure to replace it in all subprojects and all places you use it.
Replace Sleuth with Micrometer
Spring Sleuth was replaced with Micrometer, so we have to remove the old spring Sleuth started, if your project has the following dependencies, please remove them.
I have created a more detailed post about Migrating Spring Sleuth to Micrometer Observability in Spring Boot 3
Replace hibernate-types
We use PostgreSQL and a library for custom types com.vladmihalcea:hibernate-types-5
. But this library was moved to another package, so we have to replace the following
implementation("com.vladmihalcea:hibernate-types-5:2.3.5")
with the following
implementation 'io.hypersistence:hypersistence-utils-hibernate-63:3.7.3'
They are the same in a way, just the package changed. Due to this, all imports starting with
com.vladmihalcea.hibernate
becameio.hypersistence.utils.hibernate
com.vladmihalcea.spring
becameio.hypersistence.utils.spring
You can learn more here
Wiremock replacement old package
In our case, some libraries had an old version of Wiremock. the library was migrated to another group ad module, so the old one was.
testImplementation "com.github.tomakehurst:wiremock-jre8:2.35.0"
replace it with the new one
testImplementation 'org.wiremock:wiremock-standalone:3.5.2'
Java EE to Jakarta EE dependencies
With the change of the Java EE to Jakarta EE, I few libraries changed.
- ‘javax.xml.bind:jaxb-api:2.3.1’ became ‘jakarta.xml.bind:jakarta.xml.bind-api:4.0.2’
- ‘javax.persistence:javax.persistence-api:2.2’ becomes jakarta.persistence:jakarta.persistence-api:3.1.0
Other library updates that you may need to update
These are other libraries that changed and your project may or may not have them, we recommend checking your project for each of them and updating accordingly. Some of them are:
- Tomcat 10 (org.apache.tomcat.embed from 9.0.78 to 10.1.13
- Hibernate 6 ( org.hibernate:hibernate-core from 5.6.15.Final to 6.2.9.Final)
- Flyway 9 (org.flywaydb:flyway-core from 8.5.13 to 9.16.3)
- Hazelcast 5 (com.hazelcast:hazelcast from 4.2.6 to 5.2.4)
- sl4j 2 (org.slf4j:slf4j-api and org.slf4j:slf4j-simple from 1.7.36 to 2.0.12)
- Javers (org.javers:javers-spring form 6.x.x to 7.4.2
- Shedlock (net.javacrumbs.shedlock:shedlock-spring from 4.44.0 to 5.11.0
- JAXB runtime (org.glassfish.jaxb:jaxb-runtime) from 2.3.1 to 4.0.3
Compile Changes
Java EE to Jakarta EE
Most of the packages from Java EE (javax.*
) became Jakarta EE (jakarta.*
). Due to that, we need to upgrade a lot of other libraries to match this and avoid ClassNotFoundException
.
You can use IntelliJ to do most of the work for you (though it may miss some stuff). Navigate to Refactor → Migrate Packages and Classes → Java EE to Jakarta EE
.
Select the Whole project and Run
.
On the window that opens, select Do Refactor
Tracing Import
As we removed the Spring Sleuth dependency above and added Micrometer, all the imports that used sleuth will break. You can do a simple find and replace and it will help you fix almost everything.
You can find the code
import org.springframework.cloud.sleuth.
And replace it with
import io.micrometer.tracing.
These should be enough to replace almost everything.
@Type and @TypeDefs Mapping
@TypeDefs and @TypeDef have been removed and @Type annotation changed, so we cannot specify a string anymore.
We remove all the @TypeDef and @TypeDefs as you don’t need them anymore.
As for the @Type, we remove them to replace it with the class directly.
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)})
public class Company {
@Type(type = "jsonb")
Map<String, String> inviters;
}
with @Type
public class Company {
@Column(columnDefinition = "jsonb")
@Type(JsonBinaryType.class)
Map<String, String> inviters;
Repository
In case you have repositories that extend PagingAndSortingRepository
you need to replace it with ListCrudRepository
or ListPagingAndSortingRepository
. PagingAndSortingRepository
still exists, but it does not have some basic stuff like count, save, etc.
Controller Parameters
Starting in Spring 6 (Spring Boot 3) Spring no longer attempts to deduce parameter names by parsing bytecode which means that if you have something like this
public void patchAccounts(@PathVariable String id, ...)
it will break as it expects a name, like this one
public void patchAccounts(@PathVariable("id") id, ...)
This is also true for @RequestParam
where you have to specify the name now.
The best way is to fix everything in the code, but if you think that is too much, you can use an arg in the Java compiler and add the following configuration to your build.grade
tasks.withType(JavaCompile).configureEach {
options.compilerArgs.add("-parameters")
}
Feign decode404
Feign clients decode404 was removed in favor of dismiss404. You can just find and replace it in the project. According to the documentation, the behavior is the same, just a name change.
Flyway ignores missing migration
In version 8 of Flyway, the property spring.flyway.ignore-missing-migrations
was deprecated and it was removed in version 9.
To have the same effect, you can have
spring.flyway.ignore-migration-patterns=*:missing
Check for migrated and broken properties
Spring has a special dependency that you can use to detect properties that changed during the migration. Basically, you add the dependency, fix the things, and remove it.
Add the following dependency:
runtimeOnly("org.springframework.boot:spring-boot-properties-migrator")
Now you try to compile and it is gonna complain about the properties. Fix them and remove the dependency.
Unable to resolve LocalServerPort
If your plugin breaks because it cannot find the import
import org.springframework.boot.web.server.LocalServerPort
change the import to
import org.springframework.boot.test.web.server.LocalServerPort
Possible problems when running
Flyway migration stuck when it contains CONCURRENTLY
If you have a migration that contains CONCURRENTLY is running indefinitely, you have to add the following configuration to your Flyway:
Flyway flyway = Flyway.configure()
// other previous configurations
.configuration(Map.of("flyway.postgresql.transactional.lock", "false"))
.load();
you can see the discussion [here](https://github.com/spring-projects/spring-boot/issues/32629#issuecomment-2043481055
@ExceptionHandler
is returning XML instead of JSON
If your exception is returning XML instead of JSON, it means that you didn’t specify a header with content type and you probably have jackson XML in the classpath, so you have the return of something like this.
return ResponseEntity.status(BAD_REQUEST).body(error);
just add .contentType(MediaType.APPLICATION_JSON)
to the builder and it should work ok
return ResponseEntity.status(BAD_REQUEST)
.contentType(MediaType.APPLICATION_JSON)
.body(error);
HTTP Endpoints defaulting to XML
By default, we were not setting JSON as the HTTP response type, but after the upgrade it stopped doing that it started returning XML (probably because we also had a Jackson XML library in the classpath).
To set the default content type, we changed our WebMvcConfigurer to set JSON as the default.
public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer)
{
configurer.defaultContentType(MediaType.APPLICATION_JSON);
}
}
These were the primary adjustments we needed to implement to ensure that the applications functioned smoothly on Spring Boot 3.2, similar to how they operated on Spring Boot 2.7. While I’ve aimed to provide detailed descriptions, it’s worth noting that each project may have unique configurations, and certain issues may arise that aren’t covered here. Nonetheless, I hope this information proves helpful in navigating potential challenges and finding suitable solutions.
Cheers.