Skip to content

library233/move-from-java-8-to-java-11

Repository files navigation

Move from Java 8 to Java 11

Possible problems

Invalid options

A deprecated option will produce a VM warning, while an unrecognized (removed) option will cause the VM to exit.

Especially, -Xbootclasspath/p is no longer a supported option. Use --patch-module instead, which is the same effect as pre-pending the bootclasspath in Java 8. See also JEP 261.

Invalid access

A java.lang.reflect.InaccessibleObjectException indicates that you are trying to call setAccessible(true) on a field or method of an encapsulated class. For example as below.

package io.github.nvxarm.access;

import jdk.internal.loader.URLClassPath;

import java.lang.reflect.Field;

public class InaccessibleObjectExample {
    public static void main(String[] args) throws NoSuchFieldException {
        Field field = URLClassPath.class.getDeclaredField("loaders");
        field.setAccessible(true);
        System.out.println(field.getName());
    }
}

Use --add-opens to give your code access to the non-public members of a package.

java --add-opens=java.base/jdk.internal.loader=ALL-UNNAMED io.github.nvxarm.access.InaccessibleObjectExample

While a warning message like WARNING: An illegal reflective access operation has occurred means that a module has not exported the package that is being accessed through reflection. For example as below.

package io.github.nvxarm.access;

import sun.nio.ch.Util;

public class IllegalReflectiveAccessExample {
    public static void main(String[] args) {
        System.out.println(Util.getTemporaryDirectBuffer(1).capacity());
    }
}

Use --add-exports to allow the target module to access the public types of the named package of the source module.

java --add-exports=java.base/sun.nio.ch=ALL-UNNAMED io.github.nvxarm.access.IllegalReflectiveAccessExample

However, either --add-opens or --add-exports should be considered as a work-around, not a long-term solution. Using these options breaks encapsulation of the module system. Try to revise your code by removing any reference to an internal API.

Missing classes

A java.lang.NoClassDefFoundError can be caused by a split package problem, which is when a package is found in more than one library.

A java.lang.ClassNotFoundException can be thrown when a removed package is in use. To resolve the issue, add a runtime dependency to your project. Below modules were deprecated in Java 9 and removed in Java 11. See also JEP 320.

Removed modules Affected packages Suggested dependencies
Java API for XML Web Services (JAX-WS) java.xml.ws JAX WS RI Runtime (com.sun.xml.ws:jaxws-rt)
Java Architecture for XML Binding (JAXB) java.xml.bind JAXB Runtime (org.glassfish.jaxb:jaxb-runtime)
JavaBeans Activation Framework (JAV) java.activation JavaBeans Activation Framework (javax.activation:activation)
Common Annotations java.xml.ws.annotation Javax Annotation API (javax.annotation:javax.annotation-api)
Common Object Request Broker Architecture (CORBA) java.corba GlassFish CORBA ORB (org.glassfish.corba:glassfish-corba-orb)
Java Transaction API (JTA) java.transaction Java Transaction API (javax.transaction:jta)

Changed class loader

In Java 8, you can cast the system class loader to a URLClassLoader. This is usually done by applications and libraries that want to inject classes into the classpath at runtime.

The class loader hierarchy has changed in Java 11. The system class loader is now an internal class. Casting to a URLClassLoader will throw a ClassCastException at runtime. There's no such API to dynamically augment the classpath at runtime, but it can be done through reflection, with the obvious caveats about using internal API.

Also, the boot class loader only loads core modules in Java 11. If you create a class loader with a null parent, it may not find all platform classes. In Java 11, you need to pass ClassLoader.getPlatformClassLoader() instead of null as the parent class loader in such cases.

Helpful tools

Internal API analyzer - jdeps

The tool jdeps can report internal APIs that are referenced by your classes. Please note it is a "static" analysis tool. That is, dynamic dependencies (for example, by reflective access like Java SPI) can not be reported. See also the online reference.

The list below comprises some of internal APIs, with recommended replacements respectively.

Internal APIs Replacements
com.apple.eawt java.awt.Desktop since Java 9.
com.sun.image.codec.jpeg, sun.awt.image.codec javax.imageio since Java 4.
com.sun.net.ssl javax.net.ssl since Java 4.
com.sun.org.apache.xml.internal.resolver javax.xml.catalog since Java 9.
com.sun.org.apache.xml.internal.security javax.xml.crypto since Java 6.
com.sun.rowset javax.sql.rowset.RowSetProvider since Java 7.
com.sun.tools.javac javax.tools, javax.lang.model, com.sun.source since Java 6.
java.awt.peer, java.awt.dnd.peer Code block if (c.getPeer() != null) { ... } could be replaced by if (c.isDisplayable()) { ... }. To test if a component has a LightweightPeer, use public boolean isLightweight() since Java 2. To obtain the color model of the component comes from the peer, method call getPanel().getPeer().getColorModel() could be replaced by public ColorModel getColorModel().
jdk.nashorn.internal.ir JEP 236 Parser API for Nashorn.
org.relaxng.datatype org.relaxng was repackaged since Java 9. Users should include the org.relaxng types in the classpath.
org.w3c.dom.xpath org.w3c.dom., org.w3c.dom.xpath, org.w3c.dom.html, org.w3c.dom.css, org.w3c.dom.stylesheets since Java 9.
java.lang.ClassLoader.defineClass() java.lang.invoke.MethodHandles.Lookup.defineClass() since Java 9.
Security provider implementation class such as com.sun.net.ssl.internal.ssl.Provider, sun.security.provider.Sun, com.sun.crypto.provider.SunJCE java.security.Security.getProvider(NAME) since Java 3, where NAME is the security provider name such as "SUN", "SunJCE".
sun.io java.nio.charsets since Java 4.
sun.misc.BASE64Decoder, sun.misc.BASE64Encoder, com.sun.org.apache.xml.internal.security.utils.Base64 java.util.Base64 since Java 8.
sun.misc.ClassLoaderUtil java.net.URLClassLoader.close() since Java 7.
sun.misc.Cleaner java.lang.ref.PhantomReference since Java 2.
sun.misc.Service java.util.ServiceLoader since Java 6.
sun.misc.Timer java.util.Timer since Java 3.
sun.misc.Unsafe java.lang.invoke.VarHandle, java.lang.invoke.MethodHandles.Lookup.defineClass() since Java 9.
sun.reflect.Reflection.getCallerClass() java.lang.StackWalker.getCallerClass() since Java 9.
sun.security.action java.security.PrivilegedAction to call System.getProperty or other action since Java 1.
sun.security.krb5 Some provided in com.sun.security.jgss, javax.security.auth.kerkeros.EncryptionKey, javax.security.auth.kerkeros.KerberosCredMessage, javax.security.auth.kerberos.KerberosTicket.getSessionKey() since Java 9.
sun.security.provider.PolicyFile(), sun.security.provider.PolicyFile(URL) java.security.Policy.getInstance("JavaPolicy", new java.security.URIParameter(uri)) since Java 6.
sun.security.util.HostnameChecker javax.net.ssl.SSLParameters.setEndpointIdentificationAlgorithm("HTTPS" or "LDAPS") can be used to enabled hostname checking during handshaking, javax.net.ssl.HttpsURLConnection.setHostnameVerifier() can be customized hostname verifier rules for URL operations.
sun.security.util.SecurityConstants java.lang.RuntimePermission, java.net.NetPermission, or specific Permission class since Java 1.
sun.security.x509 javax.security.auth.x500.X500Principal since Java 4.
sun.util.calendar.ZoneInfo java.util.TimeZone or java.time API since Java 8.

There is a plugin for Maven, as below, which adds new goals jdkinternals and test-jdkinternals. If there is any usage detected of an internal API, the build will stop and fail.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jdeps-plugin</artifactId>
    <version>3.1.2</version>
</plugin>

There is also a (third-party) plugin for Gradle, as below, which adds a new task jdeps.

buildscript {
    dependencies {
        classpath 'org.kordamp.gradle:jdeps-gradle-plugin:0.16.0'
    }
}
apply plugin: 'org.kordamp.gradle.jdeps'

Deprecated or removed API scanner - jdeprscan

The tool jdeprscan can look for deprecated or removed APIs that are referenced by your classes. See also the online reference.

There is a (pre-release) plugin for Maven, as below, which adds new goals jdeprscan and test-jdeprscan.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jdeprscan-plugin</artifactId>
    <version>3.0.0-alpha-1</version>
</plugin>

There is also a (third-party) plugin for Gradle, as below, which adds a new task jdeprscan.

buildscript {
    dependencies {
        classpath 'org.kordamp.gradle:jdeprscan-gradle-plugin:0.10.0'
    }
}
apply plugin: 'org.kordamp.gradle.jdeprscan'

Put to good use

  1. Script-like execution

Now java can execute a single file directly, as below. You can leverage the static type language to build robust scripts. Surely, you can also take advantage of many libraries, such as java.net.http.HttpClient - the official HTTP client introduced recently, which makes it quite easy to make an HTTP request and handle the response or any exception.

// save as: google.java

import java.net.*;
import java.net.http.*;

public class Google {
    public static void main(String[] args) throws Exception {
        var request = HttpRequest.newBuilder().uri(URI.create("https://www.google.com")).build();
        var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
        var html = response.body();
        String title = html.replaceAll("(?is)(.*<title>)(.*?)(</title>.*)", "$2").replaceAll("\\R", " ");
        System.out.println("Title: " + title);
        System.out.println("URI: " + response.uri());
        System.out.println("Status: " + response.statusCode());
        response.headers().map().forEach((k, v) -> System.out.println("Header: " + k + "=" + v));
    }
}
$ java google.java

Title: Google
URI: https://www.google.com
Status: 200
Header: cache-control=[private, max-age=0]
Header: content-length=[34277]
Header: content-type=[text/html; charset=UTF-8]
Header: expires=[-1]
Header: server=[gws]

...

You can even disable GC for applications with short lifecycle, to achieve maximized throughput.

$ java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:+AlwaysPreTouch process-some-data.java
  1. Read–eval–print loop (REPL)

Using jshell, you can enter program elements one at a time, immediately see the result, and make adjustments as needed.

It can either work in a console interactively, or read a file, as below.

$ jshell

jshell> "I can eat glass and it doesn't hurt me".replaceAll("(?i)[aeiou]", "*")
$1 ==> "_ c_n __t gl_ss _nd _t d__sn't h_rt m_"
// save as: google.jsh

import java.net.*;
import java.net.http.*;

var request = HttpRequest.newBuilder().uri(URI.create("https://www.google.com")).build()
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString())
var html = response.body()
String title = html.replaceAll("(?is)(.*<title>)(.*?)(</title>.*)", "$2").replaceAll("\\R", " ")
System.out.println("Title: " + title)
System.out.println("URI: " + response.uri())
System.out.println("Status: " + response.statusCode())
response.headers().map().forEach((k, v) -> System.out.println("Header: " + k + "=" + v))

/exit
$ jshell google.jsh

Title: Google
URI: https://www.google.com
Status: 200
Header: cache-control=[private, max-age=0]
Header: content-length=[34277]
Header: content-type=[text/html; charset=UTF-8]
Header: expires=[-1]
Header: server=[gws]

...
  1. Type inference

Java compiler now looks at each variable declaration of var and determines the type. Some limitations apply. For example as below.

private void test() {
    var foo = "test";                           // ok for local variables
    System.out.println(foo);
    foo = 1;                                    // compile error: variables are still in static types
    var bar;                                    // compile error: a variable cannot be declared without initialization
    bar = 2;
    System.out.println(foo + bar);

    var path = Paths.get("test.txt");
    try (var lines = Files.lines(path)) {       // ok in try-with-resources statements
        lines.forEach(System.out::println);
    } catch (IOException e) {
        e.printStackTrace();
    }

    var users = Map.of("Alice", 18, "Bob", 17, "Carol", 19);
    var isChild = (Map.Entry<String, Integer> entry) -> entry.getValue() < 18;          // compile error: type inference is not available in Lambda expressions
    Predicate<Map.Entry<String, Integer>> isChild = entry -> entry.getValue() < 18;     // you have to give the type explicitly
    var children = users.entrySet().stream().filter(isChild).map(Map.Entry::getKey).collect(Collectors.toList());
    System.out.println(children);

    var list1 = new ArrayList<>();              // ok for diamond operators, the compiler will try to infer the most specific generic type
    list1.add("test");                          // note that "list1" will be inferred as type "ArrayList<Object>" in this case
    Object o1 = list1.get(0);                   // because there's no enough information for the compiler to infer its generic type

    List list2 = new ArrayList<>();             // mind the fact that the type "ArrayList<Object>" of "list1" is not the same as the raw type "ArrayList" of "list2"
    list2.add("test");                          // especially when you want to assign it to something that expects a generic list
    Object o2 = list2.get(0);                   // for example as below

    List<String> strings = list1;               // compile error: "ArrayList<Object>" cannot be converted to "List<String>"
    List<String> strings = list2;               // compile successful with a warning: unchecked assignment "List" to "List<String>"

    var list3 = new ArrayList<String>();        // "list3" will be inferred as type "ArrayList<String>" as expected
    list3.add("test");
    String s3 = list3.get(0);

    List<String> list4 = new ArrayList<>();     // this is how you did prior to type inference was introduced, and "list4" is almost equivalent to "list3"
    list4.add("test");
    String s4 = list4.get(0);

    list4 = List.copyOf(list3);                 // ok to assign the result to "list4", which can be any "List"
    list3 = List.copyOf(list4);                 // compile error: since the result can be any "List", therefore it cannot be assigned to "list3", which must be an "ArrayList"
}
  1. Handy utilities

Some methods are introduced for creating small collections quickly, as below.

var list = List.of("a", "b", "c");
var set = Set.of("d", "e", "f");
var map = Map.of("k1", "v1", "k2", "v2", "k3", "v3");

Please note, collections created by above methods are immutable, which will throw an UnsupportedOperationException when you are trying to modify them. Also, unlike ArrayList, HashSet and HashMap, a NullPointerException will be thrown when you are putting a null value to collections created by above methods.

Interestingly, collections created by above methods are value-based. It means that factories are free to create a new instance or return an existing instance. Hence, if we create collections with same values, they may or may not refer to the same object on the heap. For instance, in below case, list1 == list2 may or may not evaluate to true, depending on the JVM.

var list1 = List.of("a", "b", "c");
var list2 = List.of("a", "b", "c");
  1. Private methods in interface

For a better code structure inside interface, you are now able to add private methods and private static method in interfaces. Rules are as below.

  • Private interface method cannot be abstract.

  • Private method can be used only inside interface.

  • Private static method can be used inside other static and non-static interface methods.

  • Private non-static methods cannot be used inside private static methods.

Below is a summary of allowed keywords of some versions.

Keywords in interface Java 7 Java 8 Java 11
public abstract Allowed Allowed Allowed
public static Allowed Allowed
public default Allowed Allowed
private Allowed
private static Allowed
  1. Modularity

A module is a group of packages and resources along with a descriptor. By default, all packages are private in a module, and even, you cannot use reflection on classes imported from other modules.

There are 4 types of modules as below.

  • System Modules – shipped with JDK, can be listed by java --list-modules command.

  • Application Modules – your own code, defined by module-info.class which is included in the JAR you build.

  • Unnamed Module – a module to maintain backward compatibility with legacy code, comprising all classes loaded onto the classpath but not the module-path

  • Automatic Modules – generated by adding existing JAR files to the module-path, considered to be an interim compromise for plain old JAR files (those without module descriptors), so that you don't have to wait for your dependencies to be migrated to the modular structure.

Taking below as an example, build the project firstly.

$ gradle build

BUILD SUCCESSFUL in 1s
24 actionable tasks: 24 executed

Run the application without any engine and UI loaded. To shorten the java command line, paths will be exported as shell variables, and required files will be copied into one single directory.

$ module_path=modularity-example/antivirus-app/build/libs
$ module=io.github.nvxarm.antivirus.app/io.github.nvxarm.antivirus.app.AntivirusApp
$ cp -t ${module_path} modularity-example/antivirus-{engine,ui,logger}/build/libs/*.jar
$ java -p ${module_path} -m ${module}

No antivirus engine has been loaded
No antivirus UI has been loaded
No antivirus logger has been loaded

Just put a random engine there, and run the application again, with the exactly same java command line. You will see the engine is loaded by the application.

$ cp -t ${module_path} modularity-example/kaspersky-engine/build/libs/*.jar
$ java -p ${module_path} -m ${module}

No antivirus UI has been loaded
No antivirus logger has been loaded
Kaspersky engine is launched
Kaspersky engine is exiting

Then put a web UI there, with a console logger, and run the application again. You will be able to see a page opened in your default web browser. Log lines should also be formatted in your console.

$ cp -t ${module_path} modularity-example/{web-ui,console-logger,antivirus-io}/build/libs/*.jar
$ java -p ${module_path} -m ${module}

20:08:08  INFO - Kaspersky engine is launched
20:08:12  INFO - Kaspersky engine is exiting

Finally, put a desktop UI and a file logger there, with more engines to load, and run the application again. There will be a popup window, and you will be able to find log files, containing lines in a different format than the console lines.

$ cp -t ${module_path} modularity-example/{desktop-ui,file-logger,*-engine}/build/libs/*.jar
$ java -p ${module_path} -m ${module}

20:08:24  INFO - Avira engine is launched
20:08:25  INFO - Kaspersky engine is launched
20:08:26  INFO - McAfee engine is launched
20:08:30  INFO - Kaspersky engine is exiting
20:08:31  INFO - Avira engine is exiting
20:08:32  INFO - McAfee engine is exiting

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages