Migrating a Spring Boot App to Java 9 (Part 2): Modules

In part 1, we saw the compatibility issues of migrating a Spring Boot app to Java 9. Now we dig into how to do it with Project Jigsaw's modules.

By  · Tutorial
Save
19.9K Views

Last week, I tried to make a Spring Boot app — the famous Pet Clinic — Java 9 compatible. It was not easy. I had to let go of a lot of features along the way, and all in all, the only benefit I got was an improvement in String memory management.

This week, I want to continue the migration by fully embracing the Java 9 module system.

Module information in Java 9 is implemented through a module-info.java file. The first step is to create such a file at the root of the source directory, with the module name:

module org.springframework.samples.petclinic {
}


The rest of the journey can be heaven or hell. I’m fortunate to benefit from an IntelliJ IDEA license. The IDE tells exactly what class it cannot read and a wizard lets you put it in the module file. In the end, it looks like this:

module org.springframework.samples.petclinic {
    requires java.xml.bind;
    requires javax.transaction.api;
    requires validation.api;
    requires hibernate.jpa;
    requires hibernate.validator;
    requires spring.beans;
    requires spring.core;
    requires spring.context;
    requires spring.tx;
    requires spring.web;
    requires spring.webmvc;
    requires spring.data.commons;
    requires spring.data.jpa;
    requires spring.boot;
    requires spring.boot.autoconfigure;
    requires cache.api;
}


Note that module configuration in the maven-compiler-plugin and maven-surefire-plugin can be removed.

Configuration the Unassisted Way

If you happen to be in a less-than-ideal environment, the process is the following:

  1. Run mvn clean test
  2. Analyze the error in the log to get the offending package
  3. Locate the JAR of said package
  4. If the JAR is a module, add the module name to the list of required modules
  5. If not, compute the automatic module name and add it to the list of required modules

For example:

[ERROR] ~/spring-petclinic/src/main/java/org/springframework/samples/petclinic/system/CacheConfig.java:[21,16]
  package javax.cache is not visible
[ERROR]   (package javax.cache is declared in the unnamed module, but module javax.cache does not read it)


javax.cache is located in the cache-api-1.0.0.jar. It’s not a module since there’s no module-info in the JAR. The automatic module name is cache.api. Write it as a requires in the module. Rinse and repeat.

ASM Failure

Since the first part of this post, I’ve been made aware that Spring Boot 1.5 won’t be made Java 9-compatible. Let's handle that.

Bumping Spring Boot to 2.0.0.M5 requires some changes in the module dependencies:

  • hibernate.validator to org.hibernate.validator
  • validation.api to java.validation

And just when you think it might work:

Caused by: java.lang.RuntimeException
at org.springframework.asm.ClassVisitor.visitModule(ClassVisitor.java:148)


This issue has already been documented. At this point, explicitly declaring the main class resolves the issue.

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <jvmArguments>--add-modules java.xml.bind</jvmArguments>
        <mainClass>org.springframework.samples.petclinic.PetClinicApplication</mainClass>
    </configuration>
    ...
</plugin>


Javassist Failure

The app is now ready to be tested with mvn clean spring-boot:run. Unfortunately, a new exception comes our way:

2017-10-16 17:20:22.552  INFO 45661 --- [           main] utoConfigurationReportLoggingInitializer :

Error starting ApplicationContext. To display the auto-configuration report re-run your application with 'debug' enabled.
2017-10-16 17:20:22.561 ERROR 45661 --- [           main] o.s.boot.SpringApplication               :
 Application startup failed

org.springframework.beans.factory.BeanCreationException:
  Error creating bean with name 'entityManagerFactory' defined in class path resource
   [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]:
    Invocation of init method failed; nested exception is org.hibernate.boot.archive.spi.ArchiveException:
     Could not build ClassFile


A quick search redirects to an incompatibility between Java 9 and Javassist. Javassist is the culprit here. The dependency is Spring Data JPA, transitively via Hibernate. To fix it, exclude the dependency and add the latest version:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>javassist</artifactId>
            <groupId>org.javassist</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.22.0-GA</version>
    <scope>runtime</scope>
</dependency>


Fortunately, versions are compatible — at least for our usage.

It Works!

We did it! If you arrived at this point, you deserve a pat on the shoulder, a beer, or whatever you think you deserve.

Icing on the cake, the Dev Tools dependency can be re-added.

Conclusion

Migrating to Java 9 requires using Jigsaw whether you like it or not. At the very least, it means a painful trial and error search-for-the-next-fix process and removing important steps in the build process. While it’s interesting for library/framework developers to add an additional layer of access control to their internal APIs, it’s much less so for application ones. At this stage, it’s not worth it to move to Java 9.

I hope to conduct this experiment again in some months and to notice an improvement in the situation.

The complete source code for this post can be found on 

GitHub

.

Published at DZone with permission of Nicolas Fränkel, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.


Comments