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
javax
libraries 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.servlet
becomesjakarta.servlet
javax.persistence
becomesjakarta.persistence
javax.ws.rs
becomesjakarta.ws.rs
javax.xml.bind
becomesjakarta.xml.bind
(JAXB)However, not all
javax
is changed tojakarta
. 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
orFuture
return 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
@Async
annotation’s return type constraints in Spring Framework 6 (used by Spring Boot 3).The core issue:
@Async
method returned a type other thanvoid
orFuture
(includingCompletableFuture
andListenableFuture
), it might have returnednull
without throwing an error.Spring Framework 6 explicitly validates the return type of
@Async
methods. If the return type is notvoid
or aFuture
implementation, an exception is now thrown.Resolution:
void
orFuture
return type:void
.java.util.concurrent.Future
,org.springframework.util.concurrent.ListenableFuture
, orjava.util.concurrent.CompletableFuture
.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-opens
option on the command line to enable it for specific tools and libraries.The syntax for
--add-opens
is: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.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 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.location
does 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.import
declarations.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