Author Avatar

Pradeep Mishra

0

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.

  • 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.Futureorg.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.locationspring.config.additional-location, and spring.config.import declarations.

If you found this helpful, please like the blog and share the feedback in the comment.

Schedule Cron Job with time zone in Openshift (Kubernetes)

Leave a Reply