Upgrading existing source code to keep pace with evolving framework packages never seems to go as planned. Work that conceptually seems like it should take a few hours can turn into DAYS of effort chasing down build failures, new failed regression tests, etc. No perfect approach for completing an upgrade exists for all frameworks and all applications but some best practices can help make the process appear less chaotic and frustrating.
This post illustrates some of those best practices using a simple REST web service written to use Spring Boot 2.4.5 being upgraded to Spring Boot 3.2.1. The short bullet list of key tasks to perform looks like this:
- Review release notes for the new framework version and capture bullets on the required JDK version, required versions of related libraries, configuration changes required, and changes in default behavior of modules. For Spring Boot 3.2.1, release notes can be viewed at https://github.com/spring-projects/spring-boot/wiki#release-notes
- Ensure JDK compatibility with the new framework and build scripts. Use
java --version
to verify the installed Java version is compatible with the new framework. Upgrade if necessary. Consider upgrading even if not necessary if the current Java version is more than 2-3 versions old (e.g. current version is JDK 21 and you are running 15). Update the project'spom.xml
as needed to ensure compiler options specify source / target versions of Java matching the installed JDK and the new framework's required minimum. - Housekeeping - Eliminate unused imports from source files. Add the checkstyle plugin to the Maven
pom.xml
file and run the commandmvn checkstyle:checkstyle
to generate a list of unused imports. If your IDE manaages import statements automatically, this step can be skipped. - Housekeeping - Eliminate deprecated method calls. Build the latest version of the current source tree with
-Xlint
to scan for all uses of deprecated classes, methods and variables and eliminate them as a first pass using the existing framework and existing Java version. - Housekeeping - <dependency> References in pom.xml. Run the command
mvn dependency:analysis
to generate a list of potential entries that should be added or can be removed frompom.xml
. - Update the project's
pom.xml
so any <version> elements appear on the same line as <artifactId> elements for module dependencies. This has no impact on actual build functionality but makes the content easier to scan for version references when auditing compliance with required minimums. - Update version number references in
pom.xml
for all spring-boot modules from the prior version to 3.2.1. - Update version number references in
pom.xml
for all spring and spring-security modules from the prior version to 6.3.2. - Update any references to log4j2 packages to version 2.19 to fix security vulnerabilities and maintain compatibility with Spring 6.x and Spring Boot 3.x.
- Add the specially designed Spring Boot 3.x migration package to the build to examine all configuration files for any changes required for compatibility with 3.2.1. Note, this package doesn't need to be a permanent part of the new build. It is only a developer tool for catching cases where configuration variable names changed from 2.x to 3.x. Once the application is migrated and tested, this module can be removed.
- Update any existing security related annotations to directional versions in Spring Security 6.0.3. For example,
@EnableGlobalWebMethodSecurity
was renamed to@EnableWebMethodSecurity
. - Refactor any deprecated
WebSecurityFilter
class into the new directionalSecurityFilterChain
class. - If the project already uses the directional
SecurityFilterChain
class, update the existing@Bean
namedfilterChain()
to use the new lambda based syntax. Eliminate any legacy.antMatchers()
or.mvcMatchers()
calls and replace them with directionalrequestMatchers()
calls. Any calls to.authorizeRequests()
must be replaced with.authorizeHttpRequests()
.
Details for each of these are provided below.
Read the Release Notes
For frameworks like Spring or Spring Boot, each release (even point releases like 3.0.x) will have a release notes document summarizing bug fixes, new methods and new deprecations in that release. That release note will also summarize any dependencies on external libraries, language versions, etc. requiring compliance for proper builds. Perhaps one of the key categories of changes to search for are changes in default behavior provided by the framework. Functionality that used to be provided simply by including the package without extra classes or configuration may stop working if the package adopted a new default behavior or NO default behavior.
Ensuring JDK Compatibility
Spring Boot 3.x requires Java JDK 17 as a minimum. Older projects may have specified older JDK versions to maintain compatibility with other packages. To ensure actual the version of Java being used for command line work is suitable, use java --version to verify an appropriate JDK is being used.
mdh@fedora1:~/gitwork/esp32service_sb3basic $ java --version
openjdk 20 2023-03-21
OpenJDK Runtime Environment (build 20+36-2344)
OpenJDK 64-Bit Server VM (build 20+36-2344, mixed mode, sharing)
mdh@fedora1:~/gitwork/esp32service_sb3basic $
To ensure a pom.xml
doesn't continue targeting code for older JDKs, any references like this
<properties> <maven.compiler.source>16</maven.compiler.source> <maven.compiler.target>16</maven.compiler.target> </properties>
or this need to be updated.
<build> <finalName>esp32service</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>16</source> <target>16</target> <compilerArgs> <arg>-Xlint</arg> <arg>-J-Duser.language=en_us</arg> </compilerArgs> </configuration> </plugin> <plugins> </build>
Housecleaning - Eliminating Unused Imports
Source files with dozens of import statements can create confusion when debugging other types of version conflicts during platform upgrades or major functional refactoring efforts. Imports can become stale if added to support a particular approach for implementing a function then abandoned when a better final approach is chosen. Many integrated development environment tools automatically manage Java import statements, adding them as new references are added in code and removing them (or at least highlighting them) as references are removed.
If this type of feature is not used, the checkstyle plugin can be added to a project's pom.xml file as shown below.
<!-- mdh - adding this to identify unused imports in source files -->
<!-- run "mvn checkstyle:checkstyle" to generate the summary -->
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>8.30</version>
<reportSets>
<reportSet>
<reports>
<report>checkstyle</report>
</reports>
</reportSet>
</reportSets>
</plugin>
</plugins>
</reporting>
With the plugin installed, running mvn checkstyle:checkstyle
will generate a file named checkstyle-result.xml
in the ./target
directory.
[INFO] Rendering content with org.apache.maven.skins:maven-default-skin:jar:1.3 skin.
[INFO] There are 1668 errors reported by Checkstyle 9.3 with sun_checks.xml ruleset.
[WARNING] Unable to locate Source XRef to link to - DISABLED
[WARNING] Unable to locate Test Source XRef to link to - DISABLED
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7.079 s
[INFO] Finished at: 2024-01-19T20:30:49-06:00
[INFO] ------------------------------------------------------------------------
mdh@fedora1:~/gitwork/taxplanner_sb3oauth $ ls -l ./target
total 71760
-rw-rw-r--. 1 mdh mdh 363 Jan 19 20:30 checkstyle-cachefile
-rw-rw-r--. 1 mdh mdh 7463 Jan 19 20:30 checkstyle-checker.xml
-rw-rw-r--. 1 mdh mdh 336109 Jan 19 20:30 checkstyle-result.xml
drwxrwxr-x. 4 mdh mdh 92 Jan 19 19:12 classes
drwxrwxr-x. 3 mdh mdh 25 Jan 19 19:12 generated-sources
drwxrwxr-x. 3 mdh mdh 30 Jan 19 19:12 generated-test-sources
drwxrwxr-x. 2 mdh mdh 28 Jan 19 19:12 maven-archiver
drwxrwxr-x. 3 mdh mdh 35 Jan 19 19:12 maven-status
drwxrwxr-x. 4 mdh mdh 54 Jan 19 20:30 site
-rw-rw-r--. 1 mdh mdh 73060474 Jan 19 20:11 taxplanneroauth.jar
-rw-rw-r--. 1 mdh mdh 63810 Jan 19 20:11 taxplanneroauth.jar.original
drwxrwxr-x. 2 mdh mdh 6 Jan 19 19:12 test-classes
mdh@fedora1:~/gitwork/taxplanner_sb3oauth $
Housekeeping - Eliminating Deprecated Method Calls
Any upgrade from version X to Y of a framework used within an application carries the risk of requiring work to eliminate calls to methods present but deprecated in version X which are now REMOVED from version Y. Ideally, any build work should already AVOID using methods flagged as deprecated in a given release to avoid building a backlog of more refactoring work.
To ensure warnings about deprecated methods are always seen during development, Maven based Java projects can execute compile tasks with the -Xlint
option which will generate more detailed warnings during any compile of references to classes, methods and variables which are flagged for deprecation and future elimination. It is HIGHLY advised to alter all pom.xml
files as shown below to include this option when running the javac compiler:
<build>
<finalName>esp32service</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>20</source>
<target>20</target>
<compilerArgs>
<arg>-Xlint</arg>
</compilerArgs>
</configuration>
</plugin>
<plugins>
</build>
Housecleaning - <dependency> References in pom.xml
Like import statements within source files that can become cluttered during development as modules are temporarily used until alternate solution approaches eliminate their need, <dependency>
entries in pom.xml
can also accumulate over time. This can add confusing when trying to diagnose a build or run-time problem or cause actual conflicts in run-time due to inclusion of incompatible JARs which may not be necessary in the first place.
Using the command mvn dependency:analyze
to will summarize two types of <dependency> entry problems. Unreferenced dependencies are not explicitly referenced in the project's pom.xml
file but brought in automatically by transitive dependencies from other modules that ARE explicitly included. Unused dependencies ARE listed in the pom.xml
but not actually directly invoked by any of the code compiled to build the project. (Therein lies a problem...)
Most experts believe unreferenced dependencies SHOULD be added into the project for greater transparency about what the application needs. The merits of the argument may not be 100% but the consequence of ADDING unreferenced dependencies is never harmful since they ARE getting included in the build anyway.
Attempting to scrub a pom.xml
of "unreferenced" dependencies is a more time-consuming and risky endeavor. The Maven logic for identifying them is only performing a STATIC search of compiled code within the source tree for each package in each JAR. It DOES NOT analyze actual run-time behavior which can cause myapp.jar
to call someutility.jar
which in turn calls a method in otherutility.jar
that IS required for execution. However, Maven may list otherutility.jar
as unreferenced because your source code didn't call it directly.
![]() |
DO NOT run the Maven dependency check and begin blindly deleting <dependency> entries from pom.xml unless you rebuild the project and regression test it for each change. At a minimum, make sure the project jar is started after each build to verify Spring Boot resolves all of its required configuration references. It is HIGHLY likely some indirectly used JARs will be deleted and require adding back to pom.xml unless singificant care is taken during this step. |
As an example, here is a dependency check on a project using OAuth2, Log4J2 and various Spring JDBC / WebFlux functions.
mdh@fedora1:~/gitwork/taxplanner_sb3oauth $ code class="command">mvn dependency:analyze [INFO] Scanning for projects... [INFO] [INFO] -------------------------< mdhlabs:taxplanner >------------------------- [INFO] Building taxplanner Services 0.0.1-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] >>> dependency:3.6.1:analyze (default-cli) > test-compile @ taxplanner >>> [INFO] [INFO] --- resources:3.3.1:resources (default-resources) @ taxplanner --- [INFO] Copying 1 resource from src/main/resources to target/classes [INFO] Copying 6 resources from src/main/resources to target/classes [INFO] [INFO] --- compiler:3.8.1:compile (default-compile) @ taxplanner --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ taxplanner --- [INFO] skip non existing resourceDirectory /home/mdh/gitwork/taxplanner_sb3oauth/src/test/resources [INFO] [INFO] --- compiler:3.8.1:testCompile (default-testCompile) @ taxplanner --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] <<< dependency:3.6.1:analyze (default-cli) < test-compile @ taxplanner <<< [INFO] [INFO] [INFO] --- dependency:3.6.1:analyze (default-cli) @ taxplanner --- [WARNING] Used undeclared dependencies found: [WARNING] org.springframework:spring-jdbc:jar:6.1.2:compile [WARNING] org.springframework:spring-tx:jar:6.1.2:compile [WARNING] org.springframework.security:spring-security-oauth2-resource-server:jar:6.2.1:compile [WARNING] org.apache.tomcat.embed:tomcat-embed-core:jar:10.1.17:provided [WARNING] org.springframework.security:spring-security-crypto:jar:6.2.1:compile [WARNING] Unused declared dependencies found: [WARNING] org.springframework.boot:spring-boot-properties-migrator:jar:3.2.1:runtime [WARNING] org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:jar:2.6.8:compile [WARNING] org.springframework.boot:spring-boot-starter-oauth2-resource-server:jar:3.2.1:compile [WARNING] com.auth0:java-jwt:jar:4.4.0:compile [WARNING] org.springframework.boot:spring-boot-starter-webflux:jar:3.2.1:compile [WARNING] org.springframework.boot:spring-boot-starter-tomcat:jar:3.2.1:provided [WARNING] org.springframework.boot:spring-boot-starter-jdbc:jar:3.2.1:compile [WARNING] org.springframework.boot:spring-boot-starter-data-jpa:jar:3.2.1:compile [WARNING] org.apache.tomcat:tomcat-jdbc:jar:10.1.0-M17:compile [WARNING] org.mariadb.jdbc:mariadb-java-client:jar:3.0.7:compile [WARNING] org.junit.jupiter:junit-jupiter-api:jar:5.9.0:test [WARNING] com.sun.jersey:jersey-core:jar:1.19.2:compile [WARNING] com.google.code.gson:gson:jar:2.8.1:compile [WARNING] org.springframework:spring-expression:jar:6.1.3:compile [WARNING] org.springframework:spring-aop:jar:6.1.3:compile [WARNING] org.springframework.boot:spring-boot-starter-web:jar:3.2.1:compile [WARNING] org.springframework.boot:spring-boot-starter-log4j2:jar:3.2.1:compile [WARNING] org.springframework.boot:spring-boot-starter:jar:3.2.1:compile [WARNING] commons-logging:commons-logging:jar:1.2:compile [WARNING] org.apache.logging.log4j:log4j-core:jar:2.19.0:compile [WARNING] org.apache.logging.log4j:log4j-web:jar:2.19.0:compile [WARNING] junit:junit:jar:3.8.1:test [WARNING] jakarta.servlet:jakarta.servlet-api:jar:6.0.0:provided [WARNING] asm:asm:jar:3.3.1:compile [WARNING] com.sun.jersey:jersey-bundle:jar:1.19.2:compile [WARNING] org.json:json:jar:20160810:compile [WARNING] com.sun.jersey:jersey-server:jar:1.19.2:compile [WARNING] org.codehaus.jackson:jackson-mapper-asl:jar:1.9.13:compile [WARNING] com.fasterxml.jackson.core:jackson-annotations:jar:2.13.1:compile [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 2.180 s [INFO] Finished at: 2024-01-19T22:22:14-06:00 [INFO] ------------------------------------------------------------------------ mdh@fedora1:~/gitwork/taxplanner_sb3oauth $
The unreferenced dependencies in GREEN can be ADDED to the pom.xml
without incident. But if any of the "unused dependencies" in RED are deleted from the POM, the project will still BUILD but will crash with run-time errors at startup due to missing libraries for jdbc, tomcat and oauth2.
Optimize Version References in pom.xml
The normal way module dependencies are defined in Maven pom.xml
files looks like this:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>6.1.3</version> </dependency>This format reflects the hierarchy of the XML elements of a Maven build schema but prevents package NAME and package VERSION information from being quickly scanned from the command line since the name and version do not appear TOGETHER on a line that would be returned by a
grep
command. A more useful way to format dependency entries is to combine the <artifactId> and any <version> elements on the same line, like this:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>6.1.3</version> </dependency>
This doesn't change the behavior of the build configuration but will greatly simplify ongoing version administration of modules used by the build, as the next segment will demonstrate.
Updating spring-boot Versions in pom.xml
Edit pom.xml
and search for any spring-boot reference and update the version to 3.2.1. A grep of the file before might look something like this:
mdh@fedora1:~/gitwork/esp32service_sb2basic $ grep spring-boot pom.xml
<artifactId>spring-boot-starter-parent</artifactId> <version>2.4.5</version>
<artifactId>spring-boot-starter-web</artifactId> <version>2.4.5</version>
<artifactId>spring-boot-starter-logging</artifactId>
<artifactId>spring-boot-starter-log4j2</artifactId> <version>2.4.5</version>
<artifactId>spring-boot-starter</artifactId> <version>2.4.5</version>
<artifactId>spring-boot-starter-logging</artifactId>
<artifactId>spring-boot-starter-tomcat</artifactId> <version>2.4.5</version>
<artifactId>spring-boot-starter-jdbc</artifactId> <version>2.4.5</version>
<artifactId>spring-boot-starter-data-jpa</artifactId> <version>2.4.5</version>
<artifactId>spring-boot-starter-security</artifactId>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot -->
<artifactId>spring-boot</artifactId>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-autoconfigure -->
<artifactId>spring-boot-autoconfigure</artifactId>
<artifactId>spring-boot-maven-plugin</artifactId>
mdh@fedora1:~/gitwork/esp32service_sb2basic $
After updating the references for Spring Boot modules to 3.2.1, run the grep again and verify all references were changed. Again, some lines won't have a version because they represent exclusions for logging module conflicts.
mdh@fedora1:~/gitwork/esp32service_sb3basic $ grep spring-boot pom.xml
<artifactId>spring-boot-starter-parent</artifactId> <version>3.2.1</version>
<artifactId>spring-boot-properties-migrator</artifactId>
<artifactId>spring-boot-starter-web</artifactId> <version>3.2.1</version>
<artifactId>spring-boot-starter-logging</artifactId>
<artifactId>spring-boot-starter-log4j2</artifactId> <version>3.2.1</version>
<artifactId>spring-boot-starter</artifactId> <version>3.2.1</version>
<artifactId>spring-boot-starter-logging</artifactId>
<artifactId>spring-boot-starter-tomcat</artifactId> <version>3.2.1</version>
<artifactId>spring-boot-starter-jdbc</artifactId> <version>3.2.1</version>
<artifactId>spring-boot-starter-data-jpa</artifactId> <version>3.2.1</version>
<artifactId>spring-boot-starter-security</artifactId> <version>3.2.1</version>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot -->
<artifactId>spring-boot</artifactId>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-autoconfigure -->
<artifactId>spring-boot-autoconfigure</artifactId>
<artifactId>spring-boot-maven-plugin</artifactId>
mdh@fedora1:~/gitwork/esp32service_sb3basic $
Updating spring / spring-security Versions in pom.xml
Edit pom.xml
and search for any spring or spring-security references and update them to version to 6.1.3. A grep of the file before might look something like this:
mdh@fedora1:~/gitwork/esp32service_sb2basic $ grep spring pom.xml | grep artifactId | grep -v boot
<artifactId>spring-expression</artifactId> <version>5.3.14</version>
<artifactId>spring-context</artifactId> <version>5.3.14</version>
<artifactId>spring-aop</artifactId> <version>5.3.14</version>
<artifactId>spring-core</artifactId> <version>5.3.14</version>
<artifactId>spring-beans</artifactId> <version>5.3.14</version>
<artifactId>spring-web</artifactId> <version>5.3.14</version>
<artifactId>springloaded</artifactId>
mdh@fedora1:~/gitwork/esp32service_sb2basic $
The corrected "after" version of the file should contain entries like this:
mdh@fedora1:~/gitwork/esp32service_sb3basic $ grep spring pom.xml | grep artifactId | grep -v boot
<artifactId>spring-expression</artifactId> <version>6.1.3</version>
<artifactId>spring-context</artifactId> <version>6.1.3</version>
<artifactId>spring-aop</artifactId> <version>6.1.3</version>
<artifactId>spring-core</artifactId> <version>6.1.3</version>
<artifactId>spring-beans</artifactId> <version>6.1.3</version>
<artifactId>spring-web</artifactId> <version>6.1.3</version>
<artifactId>springloaded</artifactId>
mdh@fedora1:~/gitwork/esp32service_sb3basic $
Updating javax.* to jakarta.*
The core enterprise edition servlet functions required in Spring 6.x and Spring Boot 3.2.1 underwent renames from prior versions. The full before / after list of altered package names is shown below.
javax.servlet-api jakarta.servlet-api javax.persistance jakarta.persistance javax.validation jakarta.validation javax.annotation jakarta.annotation javax.transaction jakarta.transaction
Running a grep command against the pom.xml
file can confirm any references that require updates. In this example, only the main javax.servlet-api
package was used in the project.
mdh@fedora1:~/gitwork/esp32service_sb2basic $ grep javax pom.xml | grep artifactId
<artifactId>javax.servlet-api</artifactId> <version>3.1.0</version>
mdh@fedora1:~/gitwork/esp32service_sb2basic $
The new package name and version required for Spring Boot 3.x is 6.0.0.
mdh@fedora1:~/gitwork/esp32service_sb3basic $ grep jakarta pom.xml | grep artifactId
<artifactId>jakarta.servlet-api</artifactId> <version>6.0.0</version>
mdh@fedora1:~/gitwork/esp32service_sb2basic $
After making the change to pom.xml
, all of the source files in the build should be scanned for imports of these packages and edited to reflect the new jakarta root instead of javax.
mdh@fedora1:~/gitwork/esp32service_sb2basic $ grep -r javax ./src/main/java
./src/main/java/com/mdhlabs/esp32service/services/Esp32ServiceController.java:import javax.servlet.http.HttpServletRequest;
./src/main/java/com/mdhlabs/esp32service/services/Esp32ServiceController.java:import javax.servlet.http.HttpServletResponse;
./src/main/java/com/mdhlabs/esp32service/mdhBasicAuthenticationEntryPoint.java:import javax.servlet.http.HttpServletRequest;
./src/main/java/com/mdhlabs/esp32service/mdhBasicAuthenticationEntryPoint.java:import javax.servlet.http.HttpServletResponse;
mdh@fedora1:~/gitwork/esp32service_sb2basic $mdh@fedora1:~/gitwork/esp32service_sb3basic $
After correction, a grep searching for jakarta should find the same files.
mdh@fedora1:~/gitwork/esp32service_sb3basic $ grep -r jakarta ./src/main/java
./src/main/java/com/mdhlabs/esp32service/services/Esp32ServiceController.java:import jakarta.servlet.http.HttpServletRequest;
./src/main/java/com/mdhlabs/esp32service/services/Esp32ServiceController.java:import jakarta.servlet.http.HttpServletResponse;
./src/main/java/com/mdhlabs/esp32service/mdhBasicAuthenticationEntryPoint.java:import jakarta.servlet.http.HttpServletRequest;
./src/main/java/com/mdhlabs/esp32service/mdhBasicAuthenticationEntryPoint.java:import jakarta.servlet.http.HttpServletResponse;
mdh@fedora1:~/gitwork/esp32service_sb3basic $
Updating log4j2 Versions
If the project uses Log4J2 for logging in place of the default Logback module, any referenced versions of Log4J2 modules must be above version 2.17 to avoid security flaws identified in 2021 and to maintain compatibility with Spring 6.x and Spring Boot 3.x. Again, use grep to search pom.xml
for any log4j2 references and update them.
Before:
mdh@fedora1:~/gitwork/esp32service_sb2basic $ grep log4j pom.xml | grep artifactId
<artifactId>spring-boot-starter-log4j2</artifactId> <version>3.2.1</version>
<artifactId>log4j-core</artifactId> <version>2.17.0</version>
<artifactId>log4j-api</artifactId> <version>2.17.0</version>
<artifactId>log4j-web</artifactId> <version>2.17.0</version>
mdh@fedora1:~/gitwork/esp32service_sb2basic $
After:
mdh@fedora1:~/gitwork/esp32service_sb3basic $ grep log4j pom.xml | grep artifactId
<artifactId>spring-boot-starter-log4j2</artifactId> <version>3.2.1</version>
<artifactId>log4j-core</artifactId> <version>2.19.0</version>
<artifactId>log4j-api</artifactId> <version>2.19.0</version>
<artifactId>log4j-web</artifactId> <version>2.19.0</version>
mdh@fedora1:~/gitwork/esp32service_sb3basic $
Adding the Spring Boot 3.x Configuration Auditor
The Spring Boot 3.x framework includes a special module intended for use during development that will analyze all sources of configuration during startup and identify any deprecated configuration variable references that need to be altered for Spring Boot 3.x. Presumably, it will also identify configurations whose default values changed that are being used but now may require explicit configuration. The dependency for this module looks like this.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>
It only needs to be run during development and can / should be removed from builds running in production environments.
Security - Updating Annotations
A few annotations used in configuration classes and security classes were renamed as part of Spring Security 6.x which is used by Spring Boot 3.x. Specifically, the @EnableGlobalMethodSecurity
annotation is replaced with @EnableMethodSecurity
. Use grep recursively against the entire source tree to identify any source files using the old reference like this:
mdh@fedora1:~/gitwork/esp32service_sb2basic $ grep -r EnableGlobal ./src/main/java
./src/main/java/com/mdhlabs/esp32service/SecurityConfigurationSFC.java:import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
./src/main/java/com/mdhlabs/esp32service/SecurityConfigurationSFC.java:@EnableGlobalMethodSecurity(prePostEnabled = true)
mdh@fedora1:~/gitwork/esp32service_sb2basic $
Security - Eliminate WebSecurityFilter
If the existing application utilizes the deprecated class WebSecurityFilter
, logic in that class needs to be migrated to its directional replacement class of SecurityFilterChain
. The application being used as an example here did not use WebSecurityFilter
so there is no example to follow for literally migrating an existing WebSecurityFilter
class to the new approach. However, the next section provides examples of what the new class requires which provide direct visual hints at how to migrate an older scheme.
Security - Updating filterChain()
Existing apps using the deprecated WebSecurityFilter
class need to be migrated to the directional SecurityFIterChain
class. Even existing apps already using the new SecurityFilterChain
class may require additional changes to lower level configuration methods to conform to newer standards and avoid older methods which have been eliminated.
At a summary level, here are the changes required for using SecurityFilterChain
:
- The old
.authorizeRequest()
method has been eliminated and must be replaced with.authorizeHttpRequest()
. - The old
.and()
method that was used to chain configuration tasks back to a parent HttpSecurity object should be avoided and configurations should be separated into individual Java statement calls. - Any old rules using
.antMatchers()
or.mvcMatchers()
should be rewritten with the directional.requestMatcher()
method. This new method has different wildcard processing behavior for URI matching which must be CAREFULLY REVIEWED to ensure consistent endpoint security behavior between old code and updated code. The new behavior is intended to be more intuitive with less chance of leaving accidental access exposed. - The
httpBasic()
andcsrf()
methods ofHttpSecurity
have changed their underlying methods and configuration approach and require changes if used. - Each of the main configuration methods within
HttpSecurity
now allow use of lambda references to their specific configuration sub-class including a DSL expressionwithDefaults()
. Use of that expression requires the classCustomizer.withDefaults()
to be imported.
Most of these changes are easier to understand with specific examples of the before and after approaches side by side. The application used in this example is a web service using Basic authentication. It defined two different roles of USER and ADMIN then used those roles to limit access to the service endpoints based on a combination of URI and request method. It already used the SecurityFiterChain
approach by defining a class called SecurityConfigurationSFC
which defined a @Bean
named filterChain
that returns an HttpSecurity
object. In Spring Boot 2.4.5, the following implementation was used:
//--------------------------------------------------------------------------
// filterChain(HttpSecurity) - the primary method in the SecurityFilterChain
// based approach for defining security rules -- this is the new equivalent
// of the older configure(HttpSecurity) method / WebSecurityConfigurerAdapter
//--------------------------------------------------------------------------
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//--------------------------------------------------------------------------
// NOTE: These patterns are APPENDED to the servlet context /esp32service/api
// in application.properties
//--------------------------------------------------------------------------
thisLog.info("filterChain(HttpSecurity) - defining access filters for web service endpoints");
//-------------------------------------------------------------------------
// Scheme used for Spring Boot 2.x / Spring Security 5.x
// NOTE: must include "csrf().disable() for web services servlet since
// csrf() will block POST/PUT/DELETE requests lacking cookie headers as
// a cross-site scripting avoidance mechanism
//-------------------------------------------------------------------------
http.authorizeRequests()
.mvcMatchers("/datetime").hasRole("ADMIN")
.mvcMatchers(HttpMethod.POST,"/voltagereading/").hasAnyRole("USER","ADMIN")
.mvcMatchers(HttpMethod.GET ,"/voltagereading/").hasAnyRole("USER","ADMIN")
.mvcMatchers(HttpMethod.PUT, "/voltagereading/**").hasRole("ADMIN")
.mvcMatchers(HttpMethod.DELETE,"/voltagereading/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.httpBasic()
.realmName(MDHLABSREALM)
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.csrf().disable() // always disable -- doesn't apply when cookies aren't being used
;
return http.build();
}
In terms of the NEW standard schemes for configuring SecurityFilterChain in Spring Boot 3.x and its parent Spring 6.x framework, this old configuration method has a few corrections required:
authorizeRequests()
needs to be replaced withauthorizeHttpRequests()
- sub-configurations should not be chained together using the
and()
method - the old form of the
httpBasic()
method using sub-methods must be eliminated for a lambda configurator approach - the old form of the
csrf()
method using sub-methods must be eliminated for a lambda configurator approach. - the
mvcMatchers()
calls should be updated torequestMatchers()
calls.
Here's what the updated method should look like:
//--------------------------------------------------------------------------
// filterChain(HttpSecurity) - the primary method in the SecurityFilterChain
// based approach for defining security rules -- this is the new equivalent
// of the older configure(HttpSecurity) method / WebSecurityConfigurerAdapter
//--------------------------------------------------------------------------
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//--------------------------------------------------------------------------
// NOTE: These patterns are APPENDED to the servlet context /esp32service/api
// in application.properties
//--------------------------------------------------------------------------
//------------------------------------------------------------------------
// Scheme used in Spring Boot 3.x and Spring Security 6.x -- uses lamda
// based syntax to invoke the specific configurer classes for core functions
//--------------------------------------------------------------------------
//------------------------------------------------------------------------
// Scheme used in Spring Boot 3.x and Spring Security 6.x -- uses lamda
// based syntax to invoke the specific configurer classes for core functions
//--------------------------------------------------------------------------
http.authorizeHttpRequests((authorizeConfigurer) ->
authorizeConfigurer
.requestMatchers("/datetime").hasRole("ADMIN")
.requestMatchers(HttpMethod.POST,"/voltagereading/").hasAnyRole("USER","ADMIN")
.requestMatchers(HttpMethod.GET ,"/voltagereading/").hasAnyRole("USER","ADMIN")
.requestMatchers(HttpMethod.PUT, "/voltagereading/**").hasRole("ADMIN")
.requestMatchers(HttpMethod.DELETE,"/voltagereading/**").hasRole("ADMIN")
.anyRequest().authenticated()
);
http.httpBasic( (configurer) ->
configurer.authenticationEntryPoint(authenticationEntryPoint)
)
;
// csrf is ENABLED by default but not appropriate for web services not using cookies
// it must be explicitly disabled to allow POST / PUT / DELETE methods to operate
http.csrf((csrf) -> csrf.disable());
return http.build();
}
Use of ApacheClient5
Spring Framework 6.x does not support the Apache HTTP client and uses HttpClient5 instead (org.apache.httpcomponents.client5:httpclient5 ). If a project directly includes older Apache HTTP client versions, those need to be updated to version 5. It is also possible that a project may be pulling older Apache HTTP Client modules through indirect dependencies.
An easy way to identify ALL modules being included directly or indirectly into a project is to use the command mvn dependency:tree as shown below.
mdh@fedora1:~/gitwork/esp32service_sb2basic $ mvn dependency:tree
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------< mdhlabs:esp32service >------------------------
[INFO] Building esp32service 0.0.1-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- dependency:3.1.2:tree (default-cli) @ esp32service ---
[INFO] mdhlabs:esp32service:jar:0.0.1-SNAPSHOT
[INFO] +- com.google.code.gson:gson:jar:2.8.1:compile
[INFO] +- org.springframework:spring-expression:jar:5.3.14:compile
[INFO] +- org.springframework:spring-context:jar:5.3.14:compile
[INFO] +- org.springframework:spring-aop:jar:5.3.14:compile
[INFO] +- org.springframework:spring-core:jar:5.3.14:compile
[INFO] | \- org.springframework:spring-jcl:jar:5.3.6:compile
[INFO] +- org.springframework:spring-beans:jar:5.3.14:compile
[INFO] +- org.springframework:spring-web:jar:5.3.14:compile
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.4.5:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-json:jar:2.4.5:compile
[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.11.4:compile
[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.11.4:compile
[INFO] | | \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.11.4:compile
[INFO] | \- org.springframework:spring-webmvc:jar:5.3.6:compile
[INFO] +- org.springframework.boot:spring-boot-starter-log4j2:jar:2.4.5:compile
[INFO] | +- org.apache.logging.log4j:log4j-slf4j-impl:jar:2.13.3:compile
[INFO] | | \- org.slf4j:slf4j-api:jar:1.7.30:compile
[INFO] | +- org.apache.logging.log4j:log4j-jul:jar:2.13.3:compile
[INFO] | \- org.slf4j:jul-to-slf4j:jar:1.7.30:compile
[INFO] +- org.springframework.boot:spring-boot-starter:jar:2.4.5:compile
[INFO] | +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
[INFO] | \- org.yaml:snakeyaml:jar:1.27:compile
[INFO] +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.4.5:compile
[INFO] | +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.45:compile
[INFO] | +- org.glassfish:jakarta.el:jar:3.0.3:compile
[INFO] | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.45:compile
[INFO] +- org.apache.tomcat:tomcat-jdbc:jar:9.0.10:compile
[INFO] | \- org.apache.tomcat:tomcat-juli:jar:9.0.10:compile
[INFO] +- org.springframework.boot:spring-boot-starter-jdbc:jar:2.4.5:compile
[INFO] | +- com.zaxxer:HikariCP:jar:3.4.5:compile
[INFO] | \- org.springframework:spring-jdbc:jar:5.3.6:compile
[INFO] | \- org.springframework:spring-tx:jar:5.3.6:compile
[INFO] +- org.springframework.boot:spring-boot-starter-data-jpa:jar:2.4.5:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-aop:jar:2.4.5:compile
[INFO] | | \- org.aspectj:aspectjweaver:jar:1.9.6:compile
[INFO] | +- jakarta.transaction:jakarta.transaction-api:jar:1.3.3:compile
[INFO] | +- jakarta.persistence:jakarta.persistence-api:jar:2.2.3:compile
[INFO] | +- org.hibernate:hibernate-core:jar:5.4.30.Final:compile
[INFO] | | +- org.jboss.logging:jboss-logging:jar:3.4.1.Final:compile
[INFO] | | +- org.javassist:javassist:jar:3.27.0-GA:compile
[INFO] | | +- net.bytebuddy:byte-buddy:jar:1.10.22:compile
[INFO] | | +- antlr:antlr:jar:2.7.7:compile
[INFO] | | +- org.jboss:jandex:jar:2.2.3.Final:compile
[INFO] | | +- com.fasterxml:classmate:jar:1.5.1:compile
[INFO] | | +- org.dom4j:dom4j:jar:2.1.3:compile
[INFO] | | +- org.hibernate.common:hibernate-commons-annotations:jar:5.1.2.Final:compile
[INFO] | | \- org.glassfish.jaxb:jaxb-runtime:jar:2.3.4:compile
[INFO] | | +- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:compile
[INFO] | | +- org.glassfish.jaxb:txw2:jar:2.3.4:compile
[INFO] | | +- com.sun.istack:istack-commons-runtime:jar:3.0.12:compile
[INFO] | | \- com.sun.activation:jakarta.activation:jar:1.2.2:runtime
[INFO] | +- org.springframework.data:spring-data-jpa:jar:2.4.8:compile
[INFO] | | +- org.springframework.data:spring-data-commons:jar:2.4.8:compile
[INFO] | | \- org.springframework:spring-orm:jar:5.3.6:compile
[INFO] | \- org.springframework:spring-aspects:jar:5.3.6:compile
[INFO] +- org.springframework.boot:spring-boot-starter-security:jar:2.4.5:compile
[INFO] | +- org.springframework.security:spring-security-config:jar:5.4.6:compile
[INFO] | | \- org.springframework.security:spring-security-core:jar:5.4.6:compile
[INFO] | \- org.springframework.security:spring-security-web:jar:5.4.6:compile
[INFO] +- commons-logging:commons-logging:jar:1.2:compile
[INFO] +- org.apache.logging.log4j:log4j-core:jar:2.17.0:compile
[INFO] +- org.apache.logging.log4j:log4j-api:jar:2.17.0:compile
[INFO] +- org.apache.logging.log4j:log4j-web:jar:2.17.0:compile
[INFO] +- org.springframework.boot:spring-boot:jar:2.4.5:compile
[INFO] +- org.springframework.boot:spring-boot-autoconfigure:jar:2.4.5:compile
[INFO] +- org.mariadb.jdbc:mariadb-java-client:jar:2.4.3:compile
[INFO] +- junit:junit:jar:3.8.1:test
[INFO] +- javax.servlet:javax.servlet-api:jar:3.1.0:provided
[INFO] +- asm:asm:jar:3.3.1:compile
[INFO] +- com.sun.jersey:jersey-bundle:jar:1.19.2:compile
[INFO] | \- javax.ws.rs:jsr311-api:jar:1.1.1:compile
[INFO] +- org.json:json:jar:20160810:compile
[INFO] +- com.sun.jersey:jersey-server:jar:1.19.2:compile
[INFO] +- com.sun.jersey:jersey-core:jar:1.19.2:compile
[INFO] +- org.codehaus.jackson:jackson-mapper-asl:jar:1.9.13:compile
[INFO] | \- org.codehaus.jackson:jackson-core-asl:jar:1.9.13:compile
[INFO] +- com.fasterxml.jackson.core:jackson-core:jar:2.13.1:compile
[INFO] +- com.fasterxml.jackson.core:jackson-databind:jar:2.13.1:compile
[INFO] \- com.fasterxml.jackson.core:jackson-annotations:jar:2.13.1:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6.318 s
[INFO] Finished at: 2024-01-18T15:08:28-06:00
[INFO] ------------------------------------------------------------------------
mdh@fedora1:~/gitwork/esp32service_sb2basic $
After seeing an example of the detail generated in the output, obviously an easier way to use this tool tool to simply confirm the presence or absence of a module in a build is to pipe the output to grep with appropriate search keys. For example, searching for any jackson modules would return this:
mdh@fedora1:~/gitwork/esp32service_sb2basic $ mvn dependency:tree | grep jackson
[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.11.4:compile
[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.11.4:compile
[INFO] | | \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.11.4:compile
[INFO] +- org.codehaus.jackson:jackson-mapper-asl:jar:1.9.13:compile
[INFO] | \- org.codehaus.jackson:jackson-core-asl:jar:1.9.13:compile
[INFO] +- com.fasterxml.jackson.core:jackson-core:jar:2.13.1:compile
[INFO] +- com.fasterxml.jackson.core:jackson-databind:jar:2.13.1:compile
[INFO] \- com.fasterxml.jackson.core:jackson-annotations:jar:2.13.1:compile
mdh@fedora1:~/gitwork/esp32service_sb2basic $