In this blog, I will highlight the changes and challenges that I have faced while doing the migration of services from java 8 to 17 and Spring Boot 2.x to 3.x.
javax to jakarta namespace
The most significant change concerning javax libraries when migrating from Java 8 to Java 17 is the transition from Java EE (Java Platform, Enterprise Edition) to Jakarta EE.
- Package Renaming:The core change is the renaming of package names from
javax.* to jakarta.*. This affects all specifications that were part of Java EE and are now part of Jakarta EE, such as:
javax.servlet becomes jakarta.servlet
javax.persistence becomes jakarta.persistence
javax.ws.rs becomes jakarta.ws.rs
javax.xml.bind becomes jakarta.xml.bind (JAXB)
However, not all javax is changed to jakarta. For example, javax.sql.DataSource remains the same since it belongs to the standard java library. So make sure to roll back certain import statements.
@Async only supports void or Future return types
When migrating a Spring Boot application from version 2 to 3, an “invalid return type for async method” error indicates a stricter enforcement of the @Async annotation’s return type constraints in Spring Framework 6 (used by Spring Boot 3).
The core issue:
- In previous Spring versions, if an
@Async method returned a type other than void or Future (including CompletableFuture and ListenableFuture), it might have returned null without throwing an error.
- Stricter validation in Spring Boot 3 / Spring Framework 6:
Spring Framework 6 explicitly validates the return type of @Async methods. If the return type is not void or a Future implementation, an exception is now thrown.
Resolution:
- Ensure
void or Future return type:
- If the asynchronous method does not need to return a value, change its return type to
void.
- If the asynchronous method needs to return a value, ensure its return type is
java.util.concurrent.Future, org.springframework.util.concurrent.ListenableFuture, or java.util.concurrent.CompletableFuture.
// Example of a valid async method returning a CompletableFuture
@Async
public CompletableFuture<String> performAsyncTask() {
// ... asynchronous logic ...
return CompletableFuture.completedFuture("Result of async task");
}
// Example of a valid async method with no return value
@Async
public void performVoidAsyncTask() {
// ... asynchronous logic ...
}
add-opens
Some tools and libraries use reflection to access parts of the JDK that are meant for internal use only. This use of reflection negatively impacts the security and maintainability of the JDK. To aid migration, JDK 9 through JDK 16 allowed this reflection to continue, but emitted warnings about illegal reflective access. However, JDK 17 and later is strongly encapsulated, so this reflection is no longer permitted by default. Code that accesses non-public fields and methods of java.* APIs will throw an InaccessibleObjectException.
Some tools and libraries use the reflection API (setAccessible(true)) to attempt to access non-public fields and methods of java.* APIs. This is no longer possible by default on JDK 17, but you can use the --add-opens option on the command line to enable it for specific tools and libraries.
The syntax for --add-opens is:
--add-opens <module>/<package>=<target-module>(,<target-module>)*
This option allows <module> to open <package> to <target-module>, regardless of the module declaration.
As a special case, if the <target-module> is ALL-UNNAMED, then the source package is exported to all unnamed modules, whether they exist initially or are created later on. For example:
--add-opens java.management/sun.management=ALL-UNNAMED
This example allows all of the code on the class path to access nonpublic members of public types in the java.management/sun.management package.
Changed JDK and JRE Layout
After you install the JDK, if you look at the file system, you’ll notice that the directory layout is different from that of releases before JDK 9.
JDK 11 and later does not have the JRE image. See Installed Directory Structure of JDK in Java Platform, Standard Edition Installation Guide. So, make sure to give the proper path in java command.
spring.config.use-legacy-processing and ‘optional:’ in spring.config.location
Spring Boot 3.0 has removed support for legacy properties processing, including the spring.config.use-legacy-processing property that could be used in earlier versions (like Spring Boot 2.4) to revert to the old behavior. This means that if you are migrating a Spring Boot application from a version prior to 3.0 and were relying on this legacy behavior, you will need to adapt your application to the new property processing mechanisms.
In Spring Boot applications, prior to version 2.4, specifying a non-existent properties file in spring.config.location= without the optional: prefix would not cause the application to fail to start. This behavior was changed in Spring Boot 2.4 and later versions.
In Spring Boot 3, if a properties file specified in spring.config.location does not exist, the application will not start by default and will throw a ConfigDataLocationNotFoundException.
The optional: prefix is still required if you want to specify a configuration location that might not always exist and you want your application to start without an error in such cases. Without this prefix, Spring Boot will consider the non-existence of the specified file as a critical error, preventing the application from launching.
This behavior applies to spring.config.location, spring.config.additional-location, and spring.config.import declarations.
If you found this helpful, please like the blog and share the feedback in the comment.
Like this:
Like Loading...
Pradeep Mishra
Share post:
In this blog, I will highlight the changes and challenges that I have faced while doing the migration of services from java 8 to 17 and Spring Boot 2.x to 3.x.
javax to jakarta namespace
The most significant change concerning
javaxlibraries when migrating from Java 8 to Java 17 is the transition from Java EE (Java Platform, Enterprise Edition) to Jakarta EE.javax.*tojakarta.*. This affects all specifications that were part of Java EE and are now part of Jakarta EE, such as:javax.servletbecomesjakarta.servletjavax.persistencebecomesjakarta.persistencejavax.ws.rsbecomesjakarta.ws.rsjavax.xml.bindbecomesjakarta.xml.bind(JAXB)However, not all
javaxis changed tojakarta. For example,javax.sql.DataSourceremains the same since it belongs to the standard java library. So make sure to roll back certain import statements.@Async only supports
voidorFuturereturn typesWhen migrating a Spring Boot application from version 2 to 3, an “invalid return type for async method” error indicates a stricter enforcement of the
@Asyncannotation’s return type constraints in Spring Framework 6 (used by Spring Boot 3).The core issue:
@Asyncmethod returned a type other thanvoidorFuture(includingCompletableFutureandListenableFuture), it might have returnednullwithout throwing an error.Spring Framework 6 explicitly validates the return type of
@Asyncmethods. If the return type is notvoidor aFutureimplementation, an exception is now thrown.Resolution:
voidorFuturereturn type:void.java.util.concurrent.Future,org.springframework.util.concurrent.ListenableFuture, orjava.util.concurrent.CompletableFuture.// Example of a valid async method returning a CompletableFuture @Async public CompletableFuture<String> performAsyncTask() { // ... asynchronous logic ... return CompletableFuture.completedFuture("Result of async task"); } // Example of a valid async method with no return value @Async public void performVoidAsyncTask() { // ... asynchronous logic ... }add-opens
Some tools and libraries use reflection to access parts of the JDK that are meant for internal use only. This use of reflection negatively impacts the security and maintainability of the JDK. To aid migration, JDK 9 through JDK 16 allowed this reflection to continue, but emitted warnings about illegal reflective access. However, JDK 17 and later is strongly encapsulated, so this reflection is no longer permitted by default. Code that accesses non-public fields and methods of
java.*APIs will throw anInaccessibleObjectException.Some tools and libraries use the reflection API (setAccessible(true)) to attempt to access non-public fields and methods of
java.*APIs. This is no longer possible by default on JDK 17, but you can use the--add-opensoption on the command line to enable it for specific tools and libraries.The syntax for
--add-opensis:This option allows
<module>to open<package>to<target-module>, regardless of the module declaration.As a special case, if the
<target-module>isALL-UNNAMED, then the source package is exported to all unnamed modules, whether they exist initially or are created later on. For example:This example allows all of the code on the class path to access nonpublic members of public types in the
java.management/sun.managementpackage.Changed JDK and JRE Layout
After you install the JDK, if you look at the file system, you’ll notice that the directory layout is different from that of releases before JDK 9.
JDK 11 and later does not have the JRE image. See Installed Directory Structure of JDK in Java Platform, Standard Edition Installation Guide. So, make sure to give the proper path in java command.
spring.config.use-legacy-processing and ‘optional:’ in spring.config.location
Spring Boot 3.0 has removed support for legacy properties processing, including the
spring.config.use-legacy-processingproperty that could be used in earlier versions (like Spring Boot 2.4) to revert to the old behavior. This means that if you are migrating a Spring Boot application from a version prior to 3.0 and were relying on this legacy behavior, you will need to adapt your application to the new property processing mechanisms.In Spring Boot applications, prior to version 2.4, specifying a non-existent properties file in
spring.config.location=without theoptional:prefix would not cause the application to fail to start. This behavior was changed in Spring Boot 2.4 and later versions.In Spring Boot 3, if a properties file specified in
spring.config.locationdoes not exist, the application will not start by default and will throw aConfigDataLocationNotFoundException.The
optional:prefix is still required if you want to specify a configuration location that might not always exist and you want your application to start without an error in such cases. Without this prefix, Spring Boot will consider the non-existence of the specified file as a critical error, preventing the application from launching.This behavior applies to
spring.config.location,spring.config.additional-location, andspring.config.importdeclarations.If you found this helpful, please like the blog and share the feedback in the comment.
Share this:
Like this:
Build a module with its dependencies in a multi-module Maven project
INTRODUCTION The mechanism in Maven 4 that handles multi-module projects is referred to as the reactor. This part of the Maven core does the following: Module collection starts from one aggregator. read more…
Share this:
Like this:
Continue Reading
Schedule Jobs/Tasks Using Cron Expression in Spring with Example
When we have a requirement to run a task/job repeatedly after a particular time interval, we achieve this functionality by implementing Scheduling. To schedule jobs in the spring boot application to run. read more…
Share this:
Like this:
Continue Reading
Accessing Data with JPA
JPA is a specification which specifies how to access, manage and persist information/data between java objects and relational databases. It provides a standard approach for ORM, Object Relational Mapping. Spring. read more…
Share this:
Like this:
Continue Reading
Java Message Service (JMS) programming model
Before getting started with JMS programming model, I would suggest that first you go through the architecture, components and delivery model of JMS if not familiar. JMS defines an API. read more…
Share this:
Like this:
Continue Reading