Migration to Java 11

Yuri Sakhno
GitHub: YSakhno/present-migrate-to-java11

Historical Context

  • Java 8 (LTS): released on March 18, 2014
  • Java 11 (LTS): released on September 25, 2018

There were two more releases in-between:

  • Java 9: released on September 21, 2017
  • Java 10: released on March 20, 2018

As you can see, a lot of time passed, and there must be a lot of changes…

And there are!

What's New

(only major features and changes most relevant to developers follow)

Java Platform Module System (JSR 376)
(a.k.a.Project Jigsaw)

  • JDK has been modularized
  • Developers can create their own modules
  • If developers opt-in to the module system, they must explicitly specify which other modules their modules depend on, and which packages are exported
  • It is not strictly necessary to opt-in to the module system, but accessing private packages from JDK's modules might be a hurdle (even through reflection)

The Java Linker (jlink tool) can be used to assemble and optimize a set of modules and their dependencies into a custom run-time (JEP 282)

Local-Variable Type Inference (JEP 286)
a.k.a the var "keyword"

The var "keyword" can be used in place of explicit type specialization for local variables that have immediate initializer.


                        public String someMethod(int a, double b) {
                            var c = 2; // Automatically inferred as int
                            var compute = (a + c) * b; // Inferred as double
                            var result = new StringBuilder();

                            result.append("Your result is: ");
                            result.append(compute);

                            return result.toString();
                        }
                    

Can also be used in for-loops and try-with-resources statements

Compact Strings (JEP 254)

  • Internal representation of the String class changed from having a char[] field to byte[] field
  • When the string contains only characters of the ISO-8859-1 encoding (otherwise known as Latin-1), they will be stored occupying only 1 byte per character; in all other cases, the 'usual' 2-byte per character encoding (a.k.a. UTF-16) will be used
  • Data gathered from many different applications indicated that strings are a major component of heap usage and, moreover, that most strings contain only Latin-1 characters
  • This change led to reduction in memory footprint, substantial reduction of GC activity, but at the same time minor performance regressions in some corner cases

Application Class-Data Sharing (JEP 310)

Extension of the commercial CDS feature that Oracle's JVM has since Java 8.

It allows to reduce:

  • launch times
  • response time outliers
  • and (if several JVMs run on the same machine), memory footprint

New Garbage Collector: ZGC (JEP 333)

Scalable Low-Latency Garbage Collector (Experimental). Designed to meet the following goals:

  • Pause times do not exceed 10 ms
  • Pause times do not increase with the heap or live-set size
  • Handle heaps ranging from a few hundred megabytes to multi terabytes in size

JDK-8197831

Launching Single-File Source-Code Programs (JEP 330)

Having file HelloWorld.java with the following content:


                        package hello;

                        public class World {

                            public static void main(String[] args) {
                                System.out.println("Hello World!");
                            }
                        }
                    

You can run it using the java application directly:


                        $ java HelloWorld.java
                        Hello World!
                    

JDK-8192920

Other Features

  • HTTP/2 Client API available in the java.net.http package (JEP 321)
  • Support for Unicode 10 (and Unicode 9) (JEP 327)
  • Support for Transport Layer Security (TLS) 1.3 (JEP 332)
  • ChaCha20 and Poly1305 Cryptographic Algorithms (JEP 329)
  • Container Awareness The JVM can detect that it is running in a Docker container and will extract container-specific info such as number of CPUs and total memory.

Effectively-final variables in try-with-resources

Previously you had to declare new variable(s) for the purpose of using the resource it represents in the try-with-resources statement, for example:


                        public static Properties loadProperties(String filename) throws IOException {
                            try (var inputStream = new FileInputStream(new File(filename));
                                 var reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
                                final var properties = new Properties();
                                properties.load(reader);
                                return properties;
                            }
                        }
                    

Now you can simply list the variables:


                            public static Properties loadProperties(String filename) throws IOException {
                                var inputStream = new FileInputStream(filename);
                                var reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
                                try (inputStream; reader) {
                                    final var properties = new Properties();
                                    properties.load(reader);
                                    return properties;
                                }
                            }
                        

JDK-7196163

Other Language Changes

  • Allowed @SafeVargs annotation on private methods (previously it was only allowed on static or final methods, or on constructors) (JDK-7196160)
  • Allowed private methods in interfaces (JDK-8071453)
  • The underscore (_) is no longer allowed as a one-character identifier (JDK-8061549)

New APIs

Variable Handles (JEP 193)

The new class VarHandle residing in java.lang.invoke provides equivalents of java.util.concurrent.atomic and sun.misc.Unsafe operations upon object fields and array elements with similar performance.

Publish-Subscribe Framework (JEP 266)

The class java.util.concurrent.Flow provides interfaces that support the Reactive Streams publish-subscribe framework.

These interfaces support interoperability across a number of asynchronous systems running on JVMs.

Convenience Factory Methods for Collections (JEP 269)

New static factory methods on theList, Set, and Map interfaces make it simpler to create immutable instances of those collections. For example:


                        final var numbersList = List.of("one", "two", "three");
                        final var numbersSet = Set.of("one", "two", "three");
                        final var numbersMap = Map.of("one", 1, "two", 2, "three", 3);
                    

Can also create empty immutable lists / sets / maps:


                        final var emptyList = List.<String>of();
                        final var emptySet = Set.<String>of();
                        final var emptyMap = Map.<String, String>of();
                    

More APIs for Creating Unmodifiable Collections

  • The List.copyOf(), Set.copyOf() and Map.copyOf() methods create new immutable collection instances from existing instances.
  • If the source Collection / Map is immutable, calling copyOf() will generally not create a copy.
  • New methods toUnmodifiableList(), toUnmodifiableSet() and toUnmodifiableMap() added to the java.util.stream.Collectors class. These allow the elements of a Stream to be collected into an unmodifiable collection.

New Optional.stream() method

If a value is present, returns a sequential Stream containing only that value, otherwise returns an empty Stream.

This method can be used to transform a Stream of optional elements to a Stream of present value elements:


                        Stream<Optional<T>> os = ...
                        Stream<T> s = os.flatMap(Optional::stream)
                    

The not() predicate method

The java.util.function.Predicate interface has the new not() method, which negates the result of its supplied predicate (usage example will be shown on the next slide)

New methods on the String class

The following methods were added: isBlank(), lines(), strip(), stripLeading(), stripTrailing() and repeat(). Let's look at the example:


                        final var multilineString = "This is \n \u2003\r\n    \u2002a Java 11\n\tMigration Guide";

                        multilineString.lines() // Stream of lines extracted from a String,
                                                // separated by line terminators
                                .filter(not(String::isBlank)) // isBlank() returns true if string is empty
                                                              // or contains only white space codepoints
                                .map(String::strip) // akin to trim(),
                                                    // but accounts for white space in a Unicode sense
                                .forEach(System.out::println);
                    

The above snippet outputs:


                        This is
                        a Java 11
                        Migration Guide
                    

New methods on the Files class

With the inclusion of readString() and writeString() methods and their overloads, it is easier to read and write text files:


                        final String fileAsText = Files.readString("readme.txt");
                    

Removed Java EE and CORBA Modules (JEP 320)

The following modules have been removed from the JDK 11:

  • java.xml.ws (JAX-WS, plus the related technologies SAAJ and Web Services Metadata)
  • java.xml.bind (JAXB)
  • java.activation (JAF)
  • java.xml.ws.annotation (Common Annotations)
  • java.corba (CORBA)
  • java.transaction (JTA)
  • java.se.ee (aggregator module for the 6 modules above)
  • jdk.xml.ws (tools for JAX-WS)
  • jdk.xml.bind (tools for JAXB)

If you need the classes from these modules / packages in your applications, you'll need to explicitly include the external dependencies that implement them. Details later.

Upgrading to Java 11

Preparation

Download and install the build of Java 11 OpenJDK.

Here are sample flavors with corresponding links:

IntelliJ IDEA also supports automatic JDK download via its
Project Settings > Platform Settings > SDKs configuration.

There are Docker images with JDKs pre-installed,
e.g. adoptopenjdk/openjdk11

Update all the things: your IDE, your build tool, its plugins,
and your project dependencies (the latter addressed on the next slide to some extent).

Here are the recommended minimum versions for a few tools:

  • IntelliJ IDEA: 2018.2
  • Eclipse: Photon 4.9RC2 with Java 11 plugin
  • Maven: 3.5.0
    • compiler plugin: 3.8.0
    • surefire and failsafe: 2.22.0
  • Gradle: 5.0

By this time (March 2021), even these versions are outdated,
so it is probably better to just upgrade to the latest available.

When project dependencies are concerned, one major thing that comes to mind is anything that operates on the bytecode, like:

  • ASM (7.0)
  • Byte Buddy (1.9.0)
  • cglib (3.2.8)
  • Javassist (3.23.1-GA)

Additionally, the dependencies that use the ones above, like:

  • Spring (5.1)
  • Hibernate (5.3.3.Final)
  • Mockito (2.20.0)
  • many, many more…

Also, there are dependencies that cannot be upgraded,
because they do not receive support anymore, such as:

  • FindBugs (use SpotBugs instead)
  • Log4j 1 (migrate to Log4J 2)
  • Cobertura (use JaCoCo)

Upgrading to Java 11

Troubleshooting

Troubleshooting "missing classes"

Since a lot of Java EE related modules were removed, you might be getting a compilation errors similar to the following one:


                        error: package javax.xml.bind does not exist
                        import javax.xml.bind.JAXBException;
                                             ^
                    

It is also possible to get an exception at run-time:


                        Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
                            at monitor.Main.main(Main.java:27)
                        Caused by: java.lang.ClassNotFoundException: javax.xml.bind.JAXBException
                            at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582)
                            at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:185)
                            at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:496)
                            ... 1 more
                    

Troubleshooting "missing classes"
(continued)

To resolve the "missing class" errors from the previous slide, add third-party dependencies (or use the reference ones) that contain the classes you need.

Here are Maven coordinates of some such dependencies (without versions):

See also this StackOverflow answer:
https://stackoverflow.com/a/48204154/2525313

Troubleshooting Illegal Access To Internal APIs

To promote strong encapsulation, Module System of Java 9 allows
the library developers to mark certain parts of their code as "internal".

Consumers of such libraries won't be able to import classes
from such packages into their source code:


                        error: package com.sun.imageio.plugins.jpeg is not visible
                        import com.sun.imageio.plugins.jpeg.JPEG;
                                                      ^
                          (package com.sun.imageio.plugins.jpeg is declared
                          in module java.desktop, which does not export it)
                    

Troubleshooting Illegal Access To Internal APIs
(continued)

To allow for smoother transition from Java 8 to Java 11,
this limitation is relaxed to some extent for reflective access.

Instead of the "hard" error, JVM outputs a warning that looks like:


                        WARNING: An illegal reflective access operation has occurred
                        WARNING: Illegal reflective access by org.powermock.reflect.internal.WhiteboxImpl
                            (file: powermock-reflect-2.0.2.jar) to method java.lang.Object.clone()
                        WARNING: Please consider reporting this to the maintainers of org.powermock.reflect.internal.WhiteboxImpl
                        WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
                        WARNING: All illegal access operations will be denied in a future release
                    

Most notably, the above warning can be seen when running tests
(if certain libraries, such as PowerMock are used).

To find out more about why this warning occurs and what it means, see
https://stackoverflow.com/questions/50251798/what-is-an-illegal-reflective-access

Troubleshooting Illegal Access To Internal APIs
(continued 2)

There are several approaches to turning the warning off:

  1. Rewrite your code to not use illegal access
    (or do not rely on a 3rd-party library that uses such access)
  2. Open the internal package
  3. The "hacky" way (does not actually solve anything apart from hiding the message)

Troubleshooting Illegal Access To Internal APIs:
Opening the internal package

You can open a package of a module for deep reflection by using the --add-opens command line option:


                        $ java \
                                --add-opens=java.base/java.lang=application-module \
                                -jar application.jar
                    

The above command opens classes of package java.lang of module java.base to all access from module application-module.

If your code is not modularized, you can use special module
name ALL-UNNAMED, so the command above would become:


                        $ java \
                                --add-opens=java.base/java.lang=ALL-UNNAMED \
                                -jar application.jar
                    

Troubleshooting Illegal Access To Internal APIs:
The "hacky" way (several,actually)

Since the warning appears on the stderr,
you can simply redirect all such output to nowhere at the start of your program:


                        System.err.close();
                        System.setErr(System.out);
                    

Another approach is to reset internal IllegalAccessLogger
with the help of Unsafe API.


                        public static void disableIllegalAccessWarning() {
                            try {
                                Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
                                theUnsafe.setAccessible(true);
                                Unsafe u = (Unsafe) theUnsafe.get(null);

                                Class cls = Class.forName("jdk.internal.module.IllegalAccessLogger");
                                Field logger = cls.getDeclaredField("logger");
                                u.putObjectVolatile(cls, u.staticFieldOffset(logger), null);
                            } catch (Exception ignored) {}
                        }
                    

Credit: https://stackoverflow.com/a/46458447/1345053

Troubleshooting Removed APIs That Were Deprecated

Some methods or even entire classes had been deprecated for quite some time, and then finally removed in Java 11. Here are some examples:

  • sun.misc.Base64 (but you can use java.util.Base64 instead)
  • com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel (javax.swing.plaf.nimbus.NimbusLookAndFeel can be used)
  • some others too, but I doubt they are that well known to mention here

Targeting Older Versions of Java

It is still possible to compile for Java 1.6, 1.7, and 1.8 using the Java 11 JDK.

Be sure to specify the maven.compiler.release property when using Maven.
For example, the following will target the Java 1.6 platform:


                        <maven.compiler.release>6</maven.compiler.release>
                    

Gradle has similar settings (be sure to specify version as a string):


                        // In the root of build.gradle
                        sourceCompatibility = "1.8"
                        targetCompatibility = "1.8"
                    

Word(s) of caution

  • There is no way to use Java language features unsupported by older Java platform versions (it is never late to start using Kotlin though)
  • Just because you use JDK 11 for compilation, does not mean that you can use APIs that were added after the version of the platform you are targeting
    (For example, there will be no Streams if you target 1.6 or 1.7)
  • The dependencies that you use must also be compatible
    with the version of the platform you're targeting
    (well, obviously, but this is also true if you develop using any version of Java)

Questions

Scan the QR-code to get the GitHub repo link

QR code for GitHub repository

https://github.com/YSakhno/present-migrate-to-java11