7 Migrating From JDK 8 to Later JDK Releases

There were significant changes made between the JDK 8 and later JDK releases.

Every new Java SE release introduces some binary, source, and behavioral incompatibilities with previous releases. The modularization of the Java SE Platform that happened in JDK 9 and later brought many benefits, but also many changes. Code that uses only official Java SE Platform APIs and supported JDK-specific APIs should continue to work without change. Code that uses JDK-internal APIs should continue to run but should be migrated to use the supported APIs.

Some APIs that have been made inaccessible, removed, or altered in their default behavior. You might encounter issues when compiling or running your application. See Removed Tools and Components and Security Updates.

The following sections describe the changes in the JDK package that you should be aware of when migrating your JDK 8 applications to later JDK releases.

Look at the list of changes that you may encounter as you run your application.

When your application is running successfully on the latest version of JDK, review Next Steps, which will help you avoid problems with future releases.

Illegal Reflective Access

Some tools and libraries use reflection to access parts of the JDK that are meant for internal use only. This is called illegal reflective access and by default is not permitted in JDK 16 and later.

The following examples demonstrate the potential impact of the change:

  • Code successfully compiled with earlier releases that directly accesses internal APIs of the JDK will no longer work by default. For example:
    System.out.println(sun.security.util.SecurityConstants.ALL_PERMISSION);

    The compiler will generate an IllegalAccessError, and the code will throw an InaccessibileObjectException.

  • Code that uses reflection to access private fields of exported java.* APIs will no longer work by default. For example:
    var ks = java.security.KeyStore.getInstance("jceks");
    var f = ks.getClass().getDeclaredField("keyStoreSpi");
    f.setAccessible(true);

    The code will throw an InaccessibileObjectException.

  • Code that uses reflection to access protected methods of exported java.* APIs will no longer work by default. For example:
    var dc = ClassLoader.class.getDeclaredMethod("defineClass",
                                                 String.class,
                                                 byte[].class,
                                                 int.class,
                                                 int.class);
    dc.setAccessible(true);

    The code will throw an InaccessibileObjectException.

The java launcher option --illegal-access controls relaxed strong encapsulation. The default value of this option is --illegal-access=deny, which disables illegal access operations.

As a temporary solution, specify either the --illegal-access=permit or --illegal-access=warn option to continue to perform illegal access operations. To obtain detailed information about illegal reflective-access operations, including stack traces, specify the --illegal-access=debug option.

There are two options that enable you to break encapsulation (most internal APIs are encapsulated) in specific ways. You could use these in combination with the --illegal-access command-line option.
  • If you need to use an internal API that has been made inaccessible, then use the --add-exports runtime option. You can also use --add-exports at compile time to access internal APIs.
  • If you have to allow code on the class path to do deep reflection to access nonpublic members, then use the --add-opens option.

If you want to suppress all reflective access warnings, then use the --add-exports and --add-opens options where needed.

--add-exports

If you must use an internal API that has been made inaccessible by default, then you can break encapsulation using the --add-exports command-line option.

The syntax of the --add-exports option is:
--add-exports <source-module>/<package>=<target-module>(,<target-module>)*
where <source-module> and <target-module> are module names and <package> is the name of a package.

The --add-exports option allows code in the target module to access types in the named package of the source module if the target module reads the source module.

As a special case, if the <target-module> is ALL-UNNAMED, then the source package is exported to all unnamed modules, whether they exist initially or are created later on. For example:
--add-exports java.management/sun.management=ALL-UNNAMED
This example allows code in all unnamed modules (code on the class path) to access the public members of public types in java.management/sun.management. If the code on the class path attempts to do deep reflection to access nonpublic members, then the code fails.
If an application oldApp that runs on the classpath must use the unexported com.sun.jmx.remote.internal package of the java.management module, then the access that it requires can be granted in this way:
--add-exports java.management/com.sun.jmx.remote.internal=ALL-UNNAMED
You can also break encapsulation with the JAR file manifest:
Add-Exports:java.management/sun.management

Use the --add-exports option carefully. You can use it to gain access to an internal API of a library module, or even of the JDK itself, but you do so at your own risk. If that internal API changes or is removed, then your library or application fails.

See also JEP 261.

--add-opens

If you have to allow code on the class path to do deep reflection to access nonpublic members, then use the --add-opens runtime option.

Some libraries do deep reflection, meaning setAccessible(true), so they can access all members, including private ones. You can grant this access using the --add-opens option on the java command line. No warning messages are generated as a result of using this option.

If --illegal-access=deny, and you see IllegalAccessException or InaccessibleObjectException messages at runtime, you could use the --add-opens runtime option, basing the arguments upon the information shown in the exception message.

The syntax for --add-opens is:
--add-opens module/package=target-module(,target-module)*
This option allows <module> to open <package> to <target-module>, regardless of the module declaration.
As a special case, if the <target-module> is ALL-UNNAMED, then the source package is exported to all unnamed modules, whether they exist initially or are created later on. For example:
--add-opens java.management/sun.management=ALL-UNNAMED
This example allows all of the code on the class path to access nonpublic members of public types in the java.management/sun.management package.

Note:

If you are using the JNI Invocation API, including, for example, a Java Web Start JNLP file, you must include an equals sign between --add-opens and its value.
<j2se version="10" java-vm-args="--add-opens=module/package=ALL-UNNAMED"  />

The equals sign between --add-opens and its value is optional on the command line.

New Version-String Scheme

JDK 10 introduced some minor changes, to better accommodate the time-based release model, to the version-string scheme introduced in JDK 9. JDK 11 and later retains the version string format that was introduced in JDK 10.

If your code relies on the version-string format to distinguish major, minor, security, and patch update releases, then you may need to update it.

The format of the new version-string is:

$FEATURE.$INTERIM.$UPDATE.$PATCH

A simple Java API to parse, validate, and compare version strings has been added. See java.lang.Runtime.Version.

See Version String Format in Java Platform, Standard Edition Installation Guide .

For the changes to the version string introduced in JDK 9, see JEP 223: New Version-String Scheme .

For the version string changes introduced in JDK 10, see JEP 322: Time-Based Release Versioning.

Changes to the Installed JDK/JRE Image

Significant changes have been made to the JDK and JRE.

Changed JDK and JRE Layout

After you install the JDK, if you look at the file system, you’ll notice that the directory layout is different from that of releases before JDK 9.

JDK 11 and Later

JDK 11 and later does not have the JRE image. See Installed Directory Structure of JDK in Java Platform, Standard Edition Installation Guide.

JDK 9 and JDK 10

Prior releases had two types of runtime images: the JRE, which was a complete implementation of the Java SE Platform, and the JDK, which included the entire JRE in a jre/ directory, plus development tools and libraries.

In JDK 9 and and JDK 10, the JDK and JRE are two types of modular runtime images containing the following directories:

  • bin: contains binary executables.

  • conf: contains .properties, .policy, and other kinds of files intended to be edited by developers, deployers, and end users. These files were formerly found in the lib directory or its subdirectories.

  • lib: contains dynamically linked libraries and the complete internal implementation of the JDK.

In JDK 9 and JDK 10, there are still separate JDK and JRE downloads, but each has the same directory structure. The JDK image contains the extra tools and libraries that have historically been found in the JDK. There are no jdk/ versus jre/ wrapper directories, and binaries (such as the java command) aren’t duplicated.

See JEP 220: Modular Run-Time Images.

New Class Loader Implementations

JDK 9 and later releases maintain the hierarchy of class loaders that existed since the 1.2 release. However, the following changes have been made to implement the module system:

  • The application class loader is no longer an instance of URLClassLoader but, rather, of an internal class. It is the default loader for classes in modules that are neither Java SE nor JDK modules.

  • The extension class loader has been renamed; it is now the platform class loader. All classes in the Java SE Platform are guaranteed to be visible through the platform class loader.

    Just because a class is visible through the platform class loader does not mean the class is actually defined by the platform class loader. Some classes in the Java SE Platform are defined by the platform class loader while others are defined by the bootstrap class loader. Applications should not depend on which class loader defines which platform class.

    The changes that were implemented in JDK 9 may impact code that creates class loaders with null (that is, the bootstrap class loader) as the parent class loader and assumes that all platform classes are visible to the parent. Such code may need to be changed to use the platform class loader as the parent (see ClassLoader.getPlatformClassLoader).

    The platform class loader is not an instance of URLClassLoader, but, rather, of an internal class.

  • The bootstrap class loader is still built-in to the Java Virtual Machine and represented by null in the ClassLoader API. It defines the classes in a handful of critical modules, such as java.base. As a result, it defines far fewer classes than in JDK 8, so applications that are deployed with -Xbootclasspath/a or that create class loaders with null as the parent may need to change as described previously.

Removed rt.jar and tools.jar

Class and resource files previously stored in lib/rt.jar, lib/tools.jar, lib/dt.jar and various other internal JAR files are stored in a more efficient format in implementation-specific files in the lib directory.

The removal of rt.jar and similar files leads to issues in these areas:

  • Starting from JDK 9, ClassLoader.getSystemResource doesn’t return a URL pointing to a JAR file (because there are no JAR files). Instead, it returns a jrt URL, which names the modules, classes, and resources stored in a runtime image without revealing the internal structure or format of the image.

    For example:

    ClassLoader.getSystemResource("java/lang/Class.class");

    When run on JDK 8, this method returns a JAR URL of the form:

    jar:file:/usr/local/jdk8/jre/lib/rt.jar!/java/lang/Class.class

    which embeds a file URL to name the actual JAR file within the runtime image.

    A modular image doesn’t contain any JAR files, so URLs of this form make no sense. On JDK 9 and later releases, this method returns:
    jrt:/java.base/java/lang/Class.class
  • The java.security.CodeSource API and security policy files use URLs to name the locations of code bases that are to be granted specific permissions. See Policy File Syntax in Java Platform, Standard Edition Security Developer's Guide. Components of the runtime system that require specific permissions are currently identified in the conf/security/java.policy file by using file URLs.

  • Older versions of IDEs and other development tools require the ability to enumerate the class and resource files stored in a runtime image, and to read their contents directly by opening and reading rt.jar and similar files. This isn’t possible with a modular image.

Removed Extension Mechanism

In JDK 8 and earlier, the extension mechanism made it possible for the runtime environment to find and load extension classes without specifically naming them on the class path. Starting from JDK 9, if you need to use the extension classes, ensure that the JAR files are on the class path.

In JDK 9 and JDK 10, the javac compiler and java launcher will exit if the java.ext.dirs system property is set, or if the lib/ext directory exists. To additionally check the platform-specific systemwide directory, specify the -XX:+CheckEndorsedAndExtDirs command-line option. This causes the same exit behavior to occur if the directory exists and isn’t empty. The extension class loader is retained in JDK 9 (and later releases) and is specified as the platform class loader (see getPlatformClassLoader.) However, in JDK 11, this option is obsolete and a warning is issued when it is used.

The following error means that your system is configured to use the extension mechanism:

<JAVA_HOME>/lib/ext exists, extensions mechanism no longer supported; Use -classpath instead.
.Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

You’ll see a similar error if the java.ext.dirs system property is set.

To fix this error, remove the ext/ directory or the java.ext.dirs system property.

See JEP 220: Modular Run-Time Images.

Removed Endorsed Standards Override Mechanism

The java.endorsed.dirs system property and the lib/endorsed directory are no longer present. The javac compiler and java launcher will exit if either one is detected.

Starting from JDK 9, you can use upgradeable modules or put the JAR files on the class path.

This mechanism was intended for application servers to override components used in the JDK. Packages to be updated would be placed into JAR files, and the system property java.endorsed.dirs would tell the Java runtime environment where to find them. If a value for this property wasn’t specified, then the default of $JAVA_HOME/lib/endorsed was used.

In JDK 8, you can use the -XX:+CheckEndorsedAndExtDirs command-line argument to check for such directories anywhere on the system.

In JDK 9 and later releases, the javac compiler and java launcher will exit if the java.endorsed.dirs system property is set, or if the lib/endorsed directory exists.

The following error means that your system is configured to use the endorsed standards override mechanism:

<JAVA_HOME>/lib/endorsed is not supported. Endorsed standards and standalone APIs
in modular form will be supported via the concept of upgradeable modules.
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

You’ll see a similar error if the java.endorsed.dirs system property is set.

To fix this error, remove the lib/endorsed directory, or unset the java.endorsed.dirs system property.

See JEP 220: Modular Run-Time Images.

Removed macOS-Specific Features

This section includes macOS-specific features that have been removed, starting in JDK 9.

Platform-Specific Desktop Features

The java.awt.Desktop class contains replacements for the APIs in the Apple–specific com.apple.eawt and com.apple.eio packages. The new APIs supersede the macOS APIs and are platform-independent.

The APIs in the com.apple.eawt and com.apple.eio packages are encapsulated, so you won’t be able to compile against them in JDK 9 or later releases. However, they remain accessible at runtime, so existing code that is compiled to old versions continues to run. Eventually, libraries or applications that use the internal classes in the apple and com.apple packages and their subpackages will need to migrate to the new API.

The com.apple.concurrent and apple.applescript packages are removed without any replacement.

See JEP 272: Platform-Specific Desktop Features.

Removed AppleScript Engine

The AppleScript engine, a platform-specific javax.script implementation, has been removed without any replacement in the JDK.

The AppleScript engine has been mostly unusable in recent releases. The functionality worked only in JDK 7 or JDK 8 on systems that already had Apple's version of the AppleScriptEngine.jar file on the system.

Windows Registry Key Changes

The Java 11 and later installer creates Windows registry keys when installing the JDK. For JDK 16, the installer creates the following Windows registry keys:

  • “HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\JDK”

  • “HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\JDK\16”

If two versions of the JDK are installed, then two different Windows registry keys are created. For example, if JDK 15.0.1 is installed with JDK 16, then the installer creates the another Windows registry key as shown:

  • “HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\JDK”

  • “HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\JDK\16”

  • “HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\JDK\15.0.1”

Deployment

Java deployment technologies were deprecated in JDK 9 and removed in JDK 11.

Use the jlink tool introduced with JDK 9 to package and deploy dedicated runtimes rather than relying on a pre-installed system JRE.

Removed Launch-Time JRE Version Selection

The ability to request a version of the JRE that isn’t the JRE being launched at launch time is removed, starting in JDK 9.

Modern applications are typically deployed using Java Web Start (JNLP), native OS packaging systems, or active installers. These technologies have their own methods to manage the JREs needed, by finding or downloading and updating the required JRE, as needed. This makes the launcher's launch-time JRE version selection obsolete.

In the previous releases, you could specify what JRE version (or range of versions) to use when starting an application. Version selection was possible through both a command-line option and manifest entry in the application's JAR file.

Starting in JDK 9, the java launcher is modified as follows:
  • Emits an error message and exits if the -version: option is given on the command line.
  • Emits a warning message and continues if the JRE-Version manifest entry is found in a JAR file.

See JEP 231: Remove Launch-Time JRE Version Selection.

Removed Support for Serialized Applets

Starting in JDK 9, the ability to deploy an applet as a serialized object isn’t supported. With modern compression and JVM performance, there’s no benefit to deploying an applet in this way.

The object attribute of the applet tag and the object and java object applet parameter tags are ignored when starting applet.

Instead of serializing applets, use standard deployment strategies.

JNLP Specification Update

JNLP (Java Network Launch Protocol) has been updated to remove inconsistencies, make code maintenance easier, and enhance security.

JNLP has been updated as follows:

  1. &amp; instead of & in JNLP files.

    The JNLP file syntax conforms to the XML specification and all JNLP files should be able to be parsed by standard XML parsers.

    JNLP files let you specify complex comparisons. Previously, this was done by using the ampersand (&), but this isn’t supported in standard XML. If you’re using & to create complex comparisons, then replace it with &amp; in your JNLP file. &amp; is compatible with all versions of JNLP.

  2. Comparing numeric version element types against nonnumeric version element types.

    Previously, when an int version element was compared with another version element that couldn’t be parsed as an int, the version elements were compared lexicographically by ASCII value.

    Starting in JDK 9, if the element that can be parsed as an int is a shorter string than the other element, it will be padded with leading zeros before being compared lexicographically by ASCII value. This ensures there can be no circularity.

    In the case where both version comparisons and a JNLP servlet are used, you should use only numeric values to represent versions.

  3. Component extensions with nested resources in java (or j2se) elements.

    This is permitted in the specification. It was previously supported, but this support wasn’t reflected in the specification.

  4. FX XML extension.

    The JNLP specification has been enhanced to add a type attribute to application-desc element, and add the subelement param in application-desc (as it already is in applet-desc).

    This doesn’t cause problems with existing applications because the previous way of specifying a JavaFX application is still supported.

See the JNLP specification updates at JSR-056.

Changes to Garbage Collection

This section describes changes to garbage collection starting in JDK 9.

Make G1 the Default Garbage Collector

The Garbage-First Garbage Collector (G1 GC) is the default garbage collector in JDK 9 and later releases.

A low-pause collector such as G1 GC should provide a better overall experience, for most users, than a throughput-oriented collector such as the Parallel GC, which is the JDK 8 default.

See Ergonomic Defaults for G1 GC and Tunable Defaults in Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide for more information about tuning G1 GC.

Removed GC Options

The following GC combinations will cause your application to fail to start in JDK 9 and later releases:

  • DefNew + CMS
  • ParNew + SerialOld
  • Incremental CMS

The foreground mode for CMS has also been removed. The command-line flags that were removed are -Xincgc, -XX:+CMSIncrementalMode, -XX:+UseCMSCompactAtFullCollection, -XX:+CMSFullGCsBeforeCompaction, and -XX:+UseCMSCollectionPassing.

The command-line flag -XX:+UseParNewGC no longer has an effect. The ParNew flag can be used only with CMS and CMS requires ParNew. Thus, the -XX:+UseParNewGC flag has been deprecated and is eligible for removal in a future release.

See JEP 214: Remove GC Combinations Deprecated in JDK 8.

Removed Permanent Generation

The permanent generation was removed in JDK 8, and the related VM options cause a warning to be printed. You should remove these options from your scripts:

  • -XX:MaxPermSize=size

  • -XX:PermSize=size

In JDK 9 and later releases, the JVM displays a warning like this:
Java HotSpot(TM) 64-Bit Server VM warning: Ignoring option MaxPermSize; support was removed in 8.0

Tools that are aware of the permanent generation may have to be updated.

See JEP 122: Remove the Permanent Generation and JDK 9 Release Notes - Removed APIs, Features, and Options .

Changes to GC Log Output

Garbage collection (GC) logging uses the JVM unified logging framework, and there are some differences between the new and the old logs. Any GC log parsers that you’re working with will probably need to change.

You may also need to update your JVM logging options. All GC-related logging should use the gc tag (for example, —Xlog:gc), usually in combination with other tags. The —XX:+PrintGCDetails and -XX:+PrintGC options have been deprecated.

See Enable Logging with the JVM Unified Logging Framework in the Java Development Kit Tool Specifications and JEP 271: Unified GC Logging.