Archive

Posts Tagged ‘Jigsaw’
  • Migrating a Spring Boot application to Java 9 - Modules

    Java 9 Logo

    Last week, I tried to link:/migrating-to-java-9/1/[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 improvement of String memory management.

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

    == Configuring module meta-data

    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:

    [source]

    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 that:

    [source]

    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 un-assisted way

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

    . Run mvn clean test . Analyze the error in the log to get the offending package . Locate the JAR of said package . If the JAR is a module, add the module name to the list of required modules . If not, compute the automatic module name, and add it to the list of requires 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 https://twitter.com/ankinson/status/919826619044331520[Spring Boot 1.5^] won’t be made Java 9-compatible. Let’s do it.

    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 https://github.com/spring-projects/spring-boot/issues/10647[documented^]. At this point, explicitly declaring the main class resolves the issue.

    [source,xml]

    org.springframework.boot spring-boot-maven-plugin --add-modules java.xml.bind org.springframework.samples.petclinic.PetClinicApplication ...

    == 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 https://stackoverflow.com/questions/27202939/could-not-build-classfile-archiveexception[an incompatibility] between Java 9 and Javassist. Javassist is the culprit here. The dependency is required Spring Data JPA, transitively via Hibernate. To fix it, exclude the dependency, and add the latest version:

    [source,xml]

    org.springframework.boot spring-boot-starter-data-jpa javassist org.javassist org.javassist javassist 3.22.0-GA runtime

    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 to move to Java 9.

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

    Categories: Java Tags: Java 9Spring BootmodulesJigsaw
  • Migrating a Spring Boot application to Java 9 - Compatibility

    Java 9 Logo

    With the coming of Java 9, there is a lot of buzz on how to migrate applications to use the module system. Unfortunately, most of the articles written focus on simple Hello world applications. Or worse, regarding Spring applications, the sample app uses legacy practices - like XML for example. This post aims to correct that by providing a step-to-step migration guide for a non-trivial modern Spring Boot application. The sample app chosen to do that is the https://github.com/spring-projects/spring-petclinic[Spring Pet clinic^].

    There are basically 2 steps to use Java 9: first, be compatible then use the fully-fledged module system. This post aims at the former, a future post will consider the later.

    == Bumping the Java version

    Once JDK 9 is available on the target machine, the first move is to bump the java.version from 8 to 9 in the POM:

    [source,xml]

    9 </propertie> ---- Now, let's `mvn clean compile`. == Cobertura's failure The first error along the way is the following: ---- [ERROR] Failed to execute goal org.codehaus.mojo:cobertura-maven-plugin:2.7:clean (default) on project spring-petclinic: Execution default of goal org.codehaus.mojo:cobertura-maven-plugin:2.7:clean failed: Plugin org.codehaus.mojo:cobertura-maven-plugin:2.7 or one of its dependencies could not be resolved: Could not find artifact com.sun:tools:jar:0 at specified path /Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home/../lib/tools.jar -> [Help 1] ---- [quote, https://github.com/cobertura/cobertura] Cobertura is a free Java code coverage reporting tool. It requires access to the _tools.jar_ that is part of JDK 8 (and earlier). One of the changes in Java 9 is the removal of that library. Hence, that is not compatible. This is already logged as an https://github.com/cobertura/cobertura/issues/271[issue^]. Given the https://github.com/cobertura/cobertura[last commit^] on the Cobertura repository is one year old, just comment out the Cobertura Maven plugin. And think about replacing Cobertura for JaCoCo instead. == Wro4J's failure The next error is the following: ---- [ERROR] Failed to execute goal ro.isdc.wro4j:wro4j-maven-plugin:1.8.0:run (default) on project spring-petclinic: Execution default of goal ro.isdc.wro4j:wro4j-maven-plugin:1.8.0:run failed: An API incompatibility was encountered while executing ro.isdc.wro4j:wro4j-maven-plugin:1.8.0:run: java.lang.ExceptionInInitializerError: null ---- [quote, https://github.com/wro4j/wro4j] wro4j is a free and Open Source Java project which will help you to easily improve your web application page loading time. It can help you to keep your static resources (js & css) well organized, merge & minify them at run-time (using a simple filter) or build-time (using maven plugin) and has a dozen of features you may find useful when dealing with web resources. This is referenced as a https://github.com/wro4j/wro4j/issues/1039[Github issue^]. Changes have been merged, but the issue is still open for Java 9 compatibility should be part of the 2.0 release. Let's comment out Wro4J for the moment. == Compilation failure Compiling the project at this point yields the following compile-time errors: ---- /Users/i303869/projects/private/spring-petclinic/src/main/java/org/springframework/samples/petclinic/vet/Vet.java Error:(30, 22) java: package javax.xml.bind.annotation is not visible (package javax.xml.bind.annotation is declared in module java.xml.bind, which is not in the module graph) /Users/i303869/projects/private/spring-petclinic/src/main/java/org/springframework/samples/petclinic/vet/Vets.java Error:(21, 22) java: package javax.xml.bind.annotation is not visible (package javax.xml.bind.annotation is declared in module java.xml.bind, which is not in the module graph) Error:(22, 22) java: package javax.xml.bind.annotation is not visible (package javax.xml.bind.annotation is declared in module java.xml.bind, which is not in the module graph) ---- That means code on the classpath cannot access this module by default. It needs to be manually added with the `--add-modules` option of Java 9's `javac`. Within Maven, it can be set by using the _maven-compiler-plugin_: [source,xml] ---- maven-compiler-plugin 3.7.0 --add-modules java.xml.bind ---- Now the project can compile. == Test failure The next step sees the failure of unit tests to fail with `mvn test`. The cause is the same, but it's a bit harder to find. It requires checking the Surefire reports. Some contain exceptions with the following line: ---- Caused by: java.lang.ClassNotFoundException: javax.xml.bind.JAXBException ---- Again, test code cannot access the module. This time, however, the _maven-surefire-plugin_ needs to be configured: [source,xml] ---- maven-surefire-plugin 2.20.1 --add-modules java.xml.bind ---- This makes the tests work. == Packaging failure If one thinks this is the end of the road, think again. The packaging phase also fails with a rather cryptic error: ---- [ERROR] Failed to execute goal org.apache.maven.plugins:maven-jar-plugin:2.6:jar (default-jar) on project spring-petclinic: Execution default-jar of goal org.apache.maven.plugins:maven-jar-plugin:2.6:jar failed: An API incompatibility was encountered while executing org.apache.maven.plugins:maven-jar-plugin:2.6:jar: java.lang.ExceptionInInitializerError: null ... Caused by: java.lang.ArrayIndexOutOfBoundsException: 1 at org.codehaus.plexus.archiver.zip.AbstractZipArchiver.(AbstractZipArchiver.java:116) ---- This one is even harder to find: it requires a Google search to stumble upon https://stackoverflow.com/questions/36583118/is-maven-ready-for-jdk9/36605759#36605759[the solution^]. The `plexus-archiver` is to blame. Simply bumping the `maven-jar-plugin` to the latest version - 3.2 at the time of this writing will make use of a Java 9 compatible version of the archiver and will solve the issue: [source,xml] ---- maven-jar-plugin 3.0.2 ---- == Spring Boot plugin failure At this point, the project finally can be compiled, tested and packaged. The next step is to run the app through the Spring Boot Maven plugin _i.e._ `mvn spring-boot:run`. But it fails again...: ---- [INFO] --- spring-boot-maven-plugin:1.5.1.RELEASE:run (default-cli) @ spring-petclinic --- [INFO] Attaching agents: [] Exception in thread "main" java.lang.ClassCastException: java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to java.base/java.net.URLClassLoader at o.s.b.devtools.restart.DefaultRestartInitializer.getUrls(DefaultRestartInitializer.java:93) at o.s.b.devtools.restart.DefaultRestartInitializer.getInitialUrls(DefaultRestartInitializer.java:56) at o.s.b.devtools.restart.Restarter.(Restarter.java:140) at o.s.b.devtools.restart.Restarter.initialize(Restarter.java:546) at o.s.b.devtools.restart.RestartApplicationListener.onApplicationStartingEvent(RestartApplicationListener.java:67) at o.s.b.devtools.restart.RestartApplicationListener.onApplicationEvent(RestartApplicationListener.java:45) at o.s.c.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:167) at o.s.c.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139) at o.s.c.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:122) at o.s.b.context.event.EventPublishingRunListener.starting(EventPublishingRunListener.java:68) at o.s.b.SpringApplicationRunListeners.starting(SpringApplicationRunListeners.java:48) at o.s.b.SpringApplication.run(SpringApplication.java:303) at o.s.b.SpringApplication.run(SpringApplication.java:1162) at o.s.b.SpringApplication.run(SpringApplication.java:1151) at org.springframework.samples.petclinic.PetClinicApplication.main(PetClinicApplication.java:32) ---- This is a https://github.com/spring-projects/spring-boot/issues/9381[documented issue^] that Spring Boot Dev Tools v1.5 is *not* compatible with Java 9. Fortunately, this bug is fixed in Spring Boot 2.0.0.M5. Unfortunately, this specific version is not yet available at the time of this writing. So for now, let's remove the dev-tools and try to run again. It fails again, but this time the exception is a familiar one: ---- Caused by: java.lang.ClassNotFoundException: javax.xml.bind.JAXBException ---- Let's add the required argument to the _spring-boot-maven-plugin_: [source,xml] ---- org.springframework.boot spring-boot-maven-plugin --add-modules java.xml.bind ... ---- The app can finally be be launched and is accessible! == Conclusion Running a non-trivial legacy application on JDK 9 requires some effort. Worse, some important features had to be left on the way: code coverage and web performance enhancements. On the opposite side, the only meager benefit is the String memory space improvement. In the next blog post, we will try to improve the situation to actually make use of modules in the app.
    Categories: Java Tags: Java 9Spring BootmodulesJigsaw