It’s time! Migrating to Java 11
Motivation
Oracle JDK 8 will end its life in January, 2019 (153 days from now), stopping the release of public updates, so better start moving and stay up-to-date with latest JDK versions containing awesome features and security fixes.
Starting from JDK 11 and onwards, the release cycle will be a new version every 6 months and a new LTS version every 3 years (next is JDK 11).
It also worth to say that Oracle JDK released for public use will be compiled against OpenJDK source code without changes.
Will Java be paid?
No, it won't. All Java/JDK development is done in the public OpenJDK repository and all the fixes and enhancements will be pushed to upstream.
I also want to mention that there are third party companies that officially support JDK releases such as Azul and RedHat and that IBM open sourced its commercial Java/JDK implementation J9:
Migration Steps
After the release of Java 9, 10 and upcoming release of Java 11, there is a lot of open questions on how to migrate applications to use the module system. Unfortunately, most of the articles written focus on simple Hello World applications.
The goal of this post is to describe a step-by-step migration guide for a non-trivial modern Spring Boot application. The sample app chosen to do that is the
There are basically three incremental phases to fully migrate to Java 11:
- Run an existing Java application with JDK 11.
- Compile the application with Java 11.
- Modularize the application to use Module System.
If you are not ready to do all three at same time, do it in your time. This way, it will be easier to get familiar with the new version and migrate incrementally.
First you need to download and install JDK 11 for your operation system:
After you must update your favorite IDE to support Java Module System:
1. Run an existing Java application with JDK 11
Why upgrade from JDK 8 to JDK 11?
- Next LTS version (if you own a commercial license).
- Full support for Linux containers (Docker included).
- Support parallel full garbage collection on G1.
- Free Application Class-Data Sharing feature.
- Free low overhead Flight Recorder and Heap Profiler.
- Heap allocation on alternative memory devices.
- New default set of root authority certificates.
- New ZGC and Epsilon garbage collectors.
- Ahead-of-time compilation and GraalVM.
- Transport Layer Security (TLS) 1.3.
- Support for “shebang” Java files! #!/bin/java
Running your application
This is a really simple step, the application (jars) created with earlier Java versions can run on JDK 11 without major issues, except if you depends on Java EE or CORBA modules which were removed from JDK in JEP-320.
In case of missing classes, you may need to explicitly add java.activation
, java.transaction
and java.xml.bind
dependencies, and in case of class file errors you will need update Java bytecode enhancement libraries like ASM, bytebuddy, javassist or cglib.
2. Compile the application with Java 11
Why upgrade source to Java 11?
- Local variable type inference (var keyword).
- New native unmodifiable collections APIs.
- New reactive streams APIs.
- Improved streams/predicate/optional APIs.
- Improved system process API.
- Improved files API.
- Support for HTTP/2.
- Standard Java Async HTTP client.
- Multi-release JARs.
Steps
- Clone Spring PetClinic repository.
git clone [email protected]:spring-projects/spring-petclinic.git
2. Open pom.xml and update java.version property.
<java.version>11</java.version><maven.compiler.source>${java.version}</maven.compiler.source><maven.compiler.target>${java.version}</maven.compiler.target>
3. Remove cobertura-maven-plugin referencesas it’s not supported with JDK 11 and looks like it isn’t being maintained for a few years.
You can use JaCoCo which supports newer JDK versions instead.
4. Update javassist and mockito-core dependencies.
<dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.23.1-GA</version></dependency><dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>2.20.0</version> <scope>test</scope></dependency>
5. Include mockito-core dependency inside wro4j-maven-plugin dependencies section.
<plugin>...<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>2.20.0</version></dependency>...</plugin>
6. Update maven-compiler-plugin to version 3.7.0+ with latest asm dependency.
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> <configuration> <release>${java.version}</release> </configuration> <dependencies> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> <version>6.2</version> </dependency> </dependencies></plugin>
7. Update maven-surefire-plugin to version 2.21.0+ with latest asm dependency.
<plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.21.0</version> <dependencies> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> <version>6.2</version> </dependency> </dependencies></plugin>
8. Include java.xml.bind module dependency because those modules were removed in JDK 11 by JEP-320.
<dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> <version>2.4.0-b180608.0325</version></dependency>
9. Include GlassFish JAXB repositories in pom.xml.
<repositories> <repository> <id>jvnet-nexus-staging</id><url>http://maven.java.net/content/repositories/staging/</url> <layout>default</layout> </repository></repositories>
10. Run the application with spring-boot-maven-plugin.
./mvnw spring-boot:run
The application UI will be available at http://localhost:8080/
11. Package the application and run tests.
./mvnw clean package
12. Run the application using packaged executable jar.
java -jar target/spring-petclinic-2.0.0.BUILD-SNAPSHOT.jar
At this point you can compile and run your application with Java 11, but you are not using module system yet.
3. Modularize the application to use Module System
Why migrate to Module System?
- Reliable configuration — to replace the brittle, error-prone class-path mechanism with a means for program components to declare explicit dependences upon one another.
- Strong encapsulation — to allow a component to declare which of its public types are accessible to other components, and which are not.
- Create a minimal JRE image for your application.
- Decrease application memory footprint.
- Optimize application startup time.
Steps
1. Create a file named module-info.java in src/main/java directory with the following contents:
module spring.petclinic {}
Now when you try to compile the application and you will see a lot of errors like this:
Error:(19, 27) java: package org.springframework.boot is not visible(package org.springframework.boot is declared in module spring.boot, but module spring.petclinic does not read it)
That means the application behaves as a modular layout and has to wire modules for compilation and runtime.
You can use Maven dependency plugin resolve goal to list all module names currently in the classpath and add them into module-info:
./mvnw compile org.apache.maven.plugins:maven-dependency-plugin:3.1.1:resolve
Notes: The command above doesn’t exclude transitive dependencies or include JDK modules.
Unfortunately, jdeps won’t help you generate the module descriptor for many reasons, but mostly because third party libraries didn’t add module system descriptors yet and they are treated as special automatic modules.
The final module descriptor should look like this:
open module spring.petclinic { requires cache.api; requires java.activation; requires java.instrument; requires java.persistence; requires java.sql; requires java.transaction; requires java.validation; requires java.xml.bind; requires org.hibernate.validator; requires spring.beans; requires spring.boot; requires spring.boot.autoconfigure; requires spring.context; requires spring.core; requires spring.data.commons; requires spring.data.jpa; requires spring.tx; requires spring.web; requires spring.webmvc; requires jdk.unsupported;}
Notes: open keyword is mandatory due to reflection requirements by Spring Framework and Hibernate JPA and jdk.unsupported is for the sun.misc.Unsafe survivors.
2. Include maven-jar-plugin to create application jar (classes only) and copy it to modules directory.
<plugin> <artifactId>maven-jar-plugin</artifactId> <version>3.1.0</version> <configuration> <outputDirectory> ${project.build.directory}/modules </outputDirectory> </configuration></plugin>
3. Include maven-dependency-plugin to copy runtime dependencies to modules directory.
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>3.1.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory> ${project.build.directory}/modules </outputDirectory> <includeScope>runtime</includeScope> <excludeArtifactIds> spring-boot-devtools </excludeArtifactIds> </configuration> </execution> </executions></plugin>
Notes: spring-boot-devtools dependency should be excluded from modules.
4. Include java.persistence and java.transaction module dependencies which fixes automatic modules issues with their updated versions:
<dependency> <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-2.1-api</artifactId> <version>1.0.2.Final</version></dependency><dependency> <groupId>org.jboss.spec.javax.transaction</groupId> <artifactId>jboss-transaction-api_1.2_spec</artifactId> <version>1.1.1.Final</version></dependency>
Also, add a exclusion for old javax.transaction dependency from spring-boot-starter-data-jpa.
<exclusions> <exclusion> <artifactId>javax.transaction-api</artifactId> <groupId>javax.transaction</groupId> </exclusion></exclusions>
5. Modify maven-surefire-plugin configuration to disable forked process:
<configuration> <forkCount>0</forkCount></configuration>
Notes: When module-info.java is present and fork process is enabled, surefire creates a mixed classpath with modules and unnamed modules causing module visibility issues and preventing the application to start.
6. Package and test the application.
./mvnw clean package
7. Run the application using Java module system.
java --add-opens java.base/java.lang=spring.core,javassist \
--module-path target/modules \
--module spring.petclinic/org.springframework.samples.petclinic.PetClinicApplication
Notes: The — add-opens is required due to JDK reflection access requested by Spring and Hibernate dependencies.
8. You can get rid of main class specified with the module parameter if set module main-class attribute using the following command:
jar --update \ --file=target/modules/spring-petclinic-2.0.0.BUILD-SNAPSHOT.jar \ --main-class=org.springframework.samples.petclinic.PetClinicApplication
9. In order to automate the previous step, you can add exec-maven-plugin:
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.6.0</version> <executions> <execution> <id>module-main-class</id> <phase>package</phase> <goals> <goal>exec</goal> </goals> <configuration> <executable>jar</executable> <arguments> <argument> --update </argument> <argument> --file=${project.build.directory}/modules/${project.build.finalName}.jar </argument> <argument> --main-class=org.springframework.samples.petclinic.PetClinicApplication </argument> <argument> --module-version=${project.version} </argument> </arguments> </configuration> </execution> </executions></plugin>
10. Now you can run the application without explicit main-class declaration:
./mvnw clean package
java --add-opens java.base/java.lang=spring.core,javassist \
--module-path=target/modules \
--module spring.petclinic
Notes: This isn’t supported out of the box by Maven yet due to MJAR-238.
Congratulations!
Now you can migrate any application to Java 11 module system.
The full changes can be viewed in the following repository:
It also includes the changes for creating a Docker image for the JDK 11 and Java Modules application using the steps from the Create a Cloud Native Image using Java Modules post.