Java11 features and migration guide

Sridhar Rao
6 min readMay 18, 2020

Java8 came out in 2014 and it is still the most used release. According to a recent JVM ecosystem report, its usage is 64%. Java8 was loaded with a lot of goodies, such as lambda’s, streams, default methods, Instant class, Optional keyword, etc. Also, Java8 has set the stage for faster innovation in the community and a new release cadence. Java’s new LTS (Long Term Support) and STS release model is widely debated. I will try to refrain from that.

Java11 came out in September 2018 as an LTS release. That means it has 5 years of premiere support and 3 years of extended support. OpenJDK and Oracle JDK are converged with Java11. Since Oracle stopped supporting Java8, more and more teams are moving their applications to Java11. Here is a quick look at some of the new features introduced in Java 9, 10, and 11. Also, some guidance on things to look out for, during migration.

What’s new in Java 9, 10 11?

There are 100’s of new features and improvements in these 3 releases. I would try to keep the list short and focus on the ones which are relevant for most.

Project Jigsaw: This is probably the biggest change. It is intended to solve a series of problems related to dependency management inside a JAR, class loader issues, increasing JRE size, and the famous “NoClassDefFoundError” error.

In Java how a JAR depends on a series (including transitive) of other JAR’s is managed externally and it is not understood by the JVM. This could lead to “NoClassDefFoundError” as a dependency could be needed only during the runtime. Also, if the same class is available in multiple JAR’s, this could lead to issues which are hard to reproduce, because of how JVM loads classes from different JARs. This could get much more complicated if a custom class-loader is used to support the extension model.

These complexities hinder the team’s ability to upgrade open source libraries. Even though some of the upgrades are backward compatible, they may introduce unforeseen problems, which is hard to find, reproduce, and fix.

Also, before Java9, there is no easy way to restrict access to certain public methods only with-in that jar. This provides weak encapsulation and a system that is not properly modularized.

Project jigsaw attempts to solve these problems through various JEP’s. Such as modularizing the JDK, and allowing modules to declare their dependencies. Then analyze those at compile-time, launch time, and fail fast for conflicting/missing dependencies. Also, enforce strong encapsulation by enabling modules to export specific packages and keeping others private. In doing that, improves performance by having smaller classpath. This also allows organizations to build smaller JREs to meet their specific needs.

//Sample Module information file for java.sql module.
module-info.java
module java.sql {
exports java.sql; //packages
exports javax.sql;
exports javax.transaction.xa;
requires java.logging; //other modules
requires java.xml;
}

JShell: Java9 ships with an interactive tool for quick prototyping. It allows us to quickly test code, without having to wrap it with a main class and compile it. It is a convenient Read-Evaluate-Print Loop, similar to Groovy and Scala.

Private Interface Methods: Until Java8, interfaces could only have public abstract methods. This has changed with Java8, where static and default method implementations were allowed. But this introduced a new problem, where if two methods in the interface share some common logic, that needed to be duplicated. This problem is addressed in Java9 by introducing both private methods and private static methods in the interface.

Local-variable Type Inference: Similar to Scala and Kotlin, Java now supports local variable type inference. i.e. we could declare local variables without having to specify the associated type. This improves the readability of code. Type inference was already available in a limited fashion in earlier versions of Java for collections. This is an extension of that.

var authors = Book.getBook().getAuthors();
var firstAuthor = authors.stream().findFirst();
var length = firstAuthor.map(String::length).orElse(0);

Immutable Collections: Java9 introduced new static factory methods to create immutable collections, which makes code clean.

List<String> namesList = List.of(“name1”, “name2”);
Set<String> namesSet = Set.of(“name1”, “name2”);
Map<String, String> namesMap = Map.ofEntries(Map.entry(“1”,“name1”),
Map.entry(“2”, “name2”));

HTTP/2: Java9 introduced a new clean API to support HTTP/2. It is a replacement for URLConnection and has 3 classes. HttpClient, HttpRequest and HttpResponse. These are based on the builder pattern. New HttpClient supports asynchronous requests which can be canceled after the request is made.

HttpClient httpClient = HttpClient.newBuilder()
.version(Version.HTTP_2)
.build();
HttpRequest req = HttpRequest.newBuilder(
URI.create("https://google.com"))
.GET()
.build();
CompletableFuture<HttpResponse<String>> resFuture = httpClient.sendAsync(req, BodyHandlers.ofString());resFuture.thenAccept(res -> System.out.println(res.version()));
resFuture.join();

Application Class Data Sharing: CDS is available from Java8 for the system classes. This is extended to support application classes. In-general to execute bytecode, JVM needs some setup. Such as look up the class on disk, load it, verify the bytecode, and load it into its internal data structure, etc. All this takes time. When this process happens for thousands of classes, we could see the delay. Even if the application JAR does not change, this process has to be repeated for every restart. So, the idea is to do this process once, and dump it into an archive and then reuse it in future launches. This reduces the JVM start time. It is really useful in autoscaling or running lambdas.

java -XX:+UseAppCDS -Xshare:dump 
-XX:SharedClassListFile=classes.lst
-XX:SharedArchiveFile=app-cds.jsa
--class-path app.jar

HTML5 JavaDoc: In Java8, the standard doclet generated pages in HTML4.01 which does not satisfy accessibility requirements. Whereas Java11 generates pages in HTML5, which increases the semantic value of the pages and makes it easy t create accessible pages.

Process API Updates: Getting information from the system process was a bit cumbersome earlier. Java9 introduced new API’s to make this simple. Now we could easily get information about, what command is used to start the process, arguments, start time, etc.

ProcessHandle.current().info()

Other changes: Default G1 Garbage Collector, no-op garbage collector(Epsilon), Full GC collector in parallel, Improved container awareness, TLS1.3, and GraalVM are other key changes. I will probably cover those in a later post.

Tips for Upgrading to Java 11

Upgrade Process: Every time we upgrade a major version, our approach has been the same. i.e. It goes through 2 phases. During the phase 1, we upgrade the runtime, execute the full test suite, address issues, then upgrade non-production instances, monitor them for a release, then upgrade production instances with new runtime. In phase 2, do all this again, but this time, use the new compiler as well. This may sound a bit slow, but given our size, this approach has proven to be least disruptive.

Prep work: Make sure to update your tools such as IDE and build tool to the latest, so that they all support Java11. Also, you should have an easy way to switch back and forth between different java versions. I use aliasing for this. Take advantage of “jdeps” and get a sense of errors and warnings.

jdeps --jdk-internals -R --class-path ‘libs/*’ $project

Make sure to update all your mocking libraries such as Mockito, jMock, PowerMock, etc. If you are using Lombok, that needs to be updated. Also, all bytecode enhancement libraries such as cglib, javassist, or byte buddy need to be updated as well. At the time of this writing, follow versions seem to work fine.

Mockito — 3.3.3, powermock — 2.0.7, JMock — 1.49

If you are using older Maven compiler plugin (i.e. < 3.7), that needs to be updated.

Compile with Java11:

Internal API usages: During compilation, if you see any failures related to any internal API usages such as “sun.*” or “com.sun.*”, clean those up. i.e. replace those with recommended alternatives.

Ex: We had some usages related to IPAddressUtil, which are replaced with apache commons validator.

//if(IPAddressUtil.isIPv4LiteralAddress(col) || IPAddressUtil.isIPv6LiteralAddress(col)) { 
if (InetAddressValidator.getInstance().isValid(col))

You could also use the following command-line options to bypass the java module system. But be mindful that this may not be a proper solution.

-- add-exports to export a package, which makes its public types and members accessible ( javac and java)
Ex: javac --add-exports java.base/sun.security.x509=ALL-UNNAMED Main.java
java --add-exports java.base/sun.security.x509=ALL-UNNAMED Main

If you have unofficial-reflective access warnings, using the “ — add-opens” option would temporarily open up the package for reflective access.

JavaEE module usages: The following JavaEE modules are removed from Java11. If you see any usages of those, replace them with corresponding libraries, which are maintained separately now.

java.transaction; 
java.xml.bind;
java.xml.ws;
java.xml.ws.annotation;
java.corba;
java.activation;

You could also use the following command-line options to bypass the java module system. But be mindful that this may not be a proper solution.

-- add-modules adds the listed modules and their transitive dependencies to the module graph
Ex: javac --add-modules java.xml.bind Main.java
java --add-modules java.xml.bind Main
--If you want to add all EE modules
javac --add-modules java.se.ee Main.java

Modularizing your Application

It is not mandatory to modularize your application for running on Java 11. But it may be worth considering, depending on the cost/benefit analysis. Java 11 supports both the classpath and module path. So, all the dependencies can still be mentioned in the classpath. Java internally groups those JARs on the classpath as one module, called the “Unnamed module”. If you are interested in modularizing your app, you should follow the approach similar to the JDK.

--

--