Upgrading to spring boot - 3.2.x with Groovy 4
Our projects were running older version of Spring-boot, Spring-boot 2.5.14 with java11 (JDK 11). We were planning to stay that way as upgrading would divert resources that could have been used in fulfilling the client requests. We employed AWS inspector to check for critical vulnerabilities in our project. Our team was instructed to fix critical vulnerabilities on priority and other can be tackled somewhere down the line(maybe never). It was fine until we started to encounter vulnerabilities related to spring and spring-boot versions.
App Architecture
Our app had 2 parts, there was a base app which contained all the properties and classes that were being extended by the different feature apps. The base app had build.gradle
that defined various properties which were supposed to be inherited by feature apps. So the goal was clear, upgrade spring version of the base app and set feature apps to point to the latest version of base app. There was another issue, some of the apps were maintained by other teams this means the upgrade would require other teams to test for compatibility and apply fixes on those apps.
It was decided to focus on the base app, base app had a good suite of unit tests which checked various parts of logic. Therefore a successful upgrade would mean that all of the unit tests would pass even those which tested web layer.
Updating JDK & Gradle
For an uninitiated upgrading to latest Spring-boot might just look as an issue of changing the version numbers in build.gradle, but before that we would need to get supported JDK and gradle.
For JDK we decided to use java 21 because it is supported by spring 3.x and is a LTS release. It also brings support for virtual threads which we could give us a foundation to build rock solid applications. After setting up JDK , project would need proper gradle version that supports that JDK.
Moving without upgrading gradle and just adding latest spring boot in build.gradle will cause the issues as
No matching variant of org.springframework.boot:spring-boot-gradle-plugin:3.2.6
.
No matching variant of org.springframework.boot:spring-boot-gradle-plugin:3.2.6 was found. The consumer was configured to find a runtime of a library compatible with Java 11, packaged as a jar, and its dependencies declared externally, as well as attribute 'org.gradle.plugin.api-version' with value '7.0.2' but:
- Variant 'apiElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.2.6 declares a library, packaged as a jar, and its dependencies declared externally:
- Incompatible because this component declares an API of a component compatible with Java 17 and the consumer needed a runtime of a component compatible with Java 11
Official gradle compatibility matrix proposes alteast gradle 8.5 on JDK 21 but we decided on gradle 8.8 which is newer and worked fine for our use case.
To upgrade gradle you could simply update the
gradle/wrapper/
gradle-wrapper.properties
with
distributionUrl=https://services.gradle.org/distributions/gradle-8.8-bin.zip
After doing this just do
./gradlew
# gradle will start pulling the latest distribution and will give out a message
> Task :help
Welcome to Gradle 8.8.
To run a build, run gradlew <task> ...
That’s it. Latest gradle is setup.
Updating Groovy
Updating groovy was important. When we tried to run our application with target spring and java 21 with groovy 3.0.8 we encountered an error Unsupported class file major version 65
; :compileGroovy task was failing in gradle. We decided it was time to ditch org.codehaus.groovy:3.0.8
to the newer groovy 4.0.21
by org.apache
. When upgrading goorvy its recommended to use matching version of spock. We used spock to write unit tests in all of our apps. Spock version 2.4-M4-groovy-4.0
was selected as it supported groovy 4.0. Adding changes the build.gradle
was straight forward.
implementation "org.apache.groovy:groovy-all:4.0.21"
testImplementation "org.spockframework:spock-core:2.4-M4-groovy-4.0"
Updating Spring boot
We wanted to move to recentish spring boot. At the time of upgrading Spring-boot 3.3
was only a month old. We wanted to have a balance of upgrade and support; Moving a large app to a month-old framework release could have been risky due to the number of dependencies in our project and consumer projects. Therefore we decided to use spring 3.2 which was 7 months old. We saw some questions and support around it which gave us the confidence needed to move forward.
Updated these plugins in gradle.
implementation "org.springframework.boot:spring-boot-starter-web:3.2.6"
implementation "org.springframework.boot:spring-boot-starter-actuator:3.2.6"
implementation "org.springframework.boot:spring-boot-starter-aop:3.2.6"
implementation "org.springframework.boot:spring-boot-starter-jdbc:3.2.6"
implementation "org.springframework.boot:spring-boot-starter-logging:3.2.6"
implementation "org.springframework.boot:spring-boot-starter-mail:3.2.6"
implementation "org.springframework.boot:spring-boot-starter-tomcat:3.2.6"
runtimeOnly "org.springframework.boot:spring-boot-devtools:3.2.6"
Issues after upgrading Spring-boot
HttpServletRequest
Not found
When we tried to compile the app we got HttpServletRequest
not found. In fact most of the javax classes were not found. This is due to Java EE standard moving to a opensource foundation and adapting new name. Java EE (javax) is a set of specification on various media and enterprise utilities. Going forward it will be referred to Jakarta. Therefore when moving from Spring-boot 2.x to Spring-boot 3.x import headers is as follows.
Spring-boot 2.x
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
Spring-boot 3.x
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
other specifications can be found here https://jakarta.ee/specifications/
WebMvcConfigurerAdapter
Not found
You might get error along the lines
9: unable to resolve class org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
@ line 9, column 1.
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
This is because until spring 4.x, WebMvcConfigurer
was implemented by abstract class WebMvcConfigurerAdapter
which used to implement
some of the interfaces. Users were encouraged to extend WebMvcConfigurerAdapter
. From spring 5.x by leveraging default-functions in the interfaces spring team has lifted up functions back to WebMvcConfigurer
hence there is no need to extend WebMvcConfigurerAdapter
.
Now instead of
@Configuration
public WebConfig extends WebMvcConfigurerAdapter {
// ...
}
You should do
@Configuration
public WebConfig implements WebMvcConfigurer {
// ...
}
Groovy Issues
We used to use a lot of @compilestatic
as it has been observed that statically compiled code works faster than dynamic groovy code. Issues that we started to observe was related to types where type influencing just broke at some places, we were also seeing issues with typecasting.
Example of such issues in existing code:
Set<CacheRefreshService> cacheRefreshServices = Collections.synchronizedSet(new HashSet<>())
Incompatible generic argument types.
Cannot assign java.util.Set<java.lang.Object> to: java.util.Set
We fixed it by
Set<CacheRefreshService> cacheRefreshServices = Collections.synchronizedSet(new HashSet<CacheRefreshService>())
It seems that enabling @compilestatic
has an effect of not enabling groovy's inferencing.
Conclusion
Updating Spring-boot started as a requirement from security team, but the process of upgrading Spring-boot and groovy gave us an overall outlook on how the things are progressing in this ecosystems. The learnings from groovy upgrade has caused us to rethink the way we structure code with @compilestatic. The latest trend of java introducing virtual threads and overall progress that the graal-vm has made in terms of a standalone AOT compiled binary has led us to explore this way of deployment. Overall, I am confident with increase in use of virtual threads and modern tooling Spring-boot / java will become competitive in terms of memory use to other newer languages and ecosystems.
Subscribe to my newsletter
Read articles from Himanshu Tripathi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by