6 Preparing for Migration

Tip:

Evaluate the Effort of Migration with Java Migration Analysis

Oracle offers Java SE Subscribers and Oracle Cloud Infrastructure (OCI) users the Java Migration Analysis feature in the Java Management Service. This feature performs a comprehensive analysis of the effort involved in migrating to a new JDK version. It generates detailed reports highlighting the effort required and areas needing modification for migrating from an older JDK version to a newer one. During the migration process, you can utilize the analysis reports from Migration Analysis to identify classes and APIs that require changes. The reports specify line numbers in the source code where modifications are needed and highlight both mandatory and recommended changes.

The following sections will help you successfully migrate your application:

Download the Latest JDK

Download and install the latest JDK release from Java SE Downloads.

Run Your Program Before Recompiling

Try running your application on the latest JDK release (JDK 23). Most code and libraries should work on JDK 23 without any changes, but there may be some libraries that need to be upgraded.

Note:

Migrating is an iterative process. You’ll probably find it best to try running your program (this task) first, then complete these three tasks in parallel:

When you run your application, look for warnings from the JVM about obsolete VM options. If the VM fails to start, then look for Removed GC Options.

If your application starts successfully, look carefully at your tests and ensure that the behavior is the same as on the JDK version you have been using. For example, a few early adopters have noticed that their dates and currencies are formatted differently. See Migrate Away From the COMPAT Locale Data Provider.

To make your code work on the latest JDK release, understand the new features and changes in each of the JDK release.

Even if your program appears to run successfully, you should complete the rest of the steps in this guide and review the list of issues.

Update Third-Party Libraries

For every tool and third-party library that you use, you may need to have an updated version that supports the latest JDK release.

Check the websites for your third-party libraries and your tool vendors for a version of each library or tool that’s designed to work on the latest JDK. If one exists, then download and install the new version.

If you use Maven or Gradle to build your application, then make sure to upgrade to a recent version that supports the latest JDK version.

If you use an IDE to develop your applications, then it might help in migrating the existing code. The NetBeans, Eclipse, and IntelliJ IDEs all have versions available that include support for the latest JDK.

You can see the status of the testing of many Free Open Source Software (FOSS)  projects with OpenJDK builds at Quality Outreach on the OpenJDK wiki.

Compile Your Application if Needed

Compiling your code with the latest JDK compiler will ease migration to future releases since the code may depend on APIs and features, which have been identified as problematic. However, it is not strictly necessary.

If you need to compile your code with JDK 11 and later compilers, then take note of the following:

  • Use the new --release flag instead of the -source and -target options. See javac in Java Development Kit Tool Specifications.

    The supported values of --release are the current Java SE release and a limited number of previous releases, detailed in the command-line help.

    The javac can recognize and process class files of all previous JDKs, going all the way back to JDK 1.0.2 class files.

    See JEP 182: Policy for Retiring javac -source and -target Options.

  • If you use the -source and -target options with javac, then check the values that you use.

    The supported -source/-target values are 23 (the default) till 9. The value 8 is deprecated and causes a warning.

    In JDK 8, -source and -target values of 1.7/7 and earlier were deprecated, and caused a warning. In JDK 9 and above, those values cause an error.

    >javac -source 7 -target 7 Sample.java 
    warning: [options] bootstrap class path is not set in conjunction with -source 7
      not setting the bootstrap class path may lead to class files that cannot run on JDK 8
        --release 7 is recommended instead of -source 7 -target 7 because it sets the bootstrap class path automatically
    error: Source option 7 is no longer supported. Use 8 or later.
    error: Target option 7 is no longer supported. Use 8 or later.

    Note:

    When using --release, you cannot also use the --source/-source or --target/ -target options.
  • If you use the underscore character (_) as a one-character identifier in source code, then your code won’t compile in JDK 11 and later releases. It generates a warning in JDK 8, and an error, starting from JDK 9.

    As an example:

    static Object _ = new Object();

    This code generates the following error message from the compiler:

    MyClass.java:2: error: as of release 9, '_' is a keyword, and may not be used as a legal identifier.
    
  • Critical internal JDK APIs such as sun.misc.Unsafe are still accessible in JDK 11 and later, but most of the JDK’s internal APIs are not accessible at compile time. You may get compilation errors that indicate that your application or its libraries are dependent on internal APIs.

    To identify the dependencies, run the Java Dependency Analysis tool. See Run jdeps on Your Code. If possible, update your code to use the supported replacement APIs.

    You may use the --add-exports and --add-opens options as a temporary workaround to compile source code with references to JDK internal classes. See JEP 261: Module System and Strong Encapsulation in the JDK for more information about these options.

  • You may see more deprecation warnings than previously.

Run jdeps on Your Code

Run the jdeps tool on your application to see what packages and classes your applications and libraries depend on. If you use internal APIs, then jdeps may suggest replacements to help you to update your code.

To look for dependencies on internal JDK APIs, run jdeps with the -jdkinternals option. For example, if you run jdeps on a class that calls sun.misc.BASE64Encoder, you’ll see:

>jdeps -jdkinternals Sample.class
Sample.class -> JDK removed internal API
   Sample  -> sun.misc.BASE64Encoder  JDK internal API (JDK removed internal API)

Warning: JDK internal APIs are unsupported and private to JDK implementation that are
subject to be removed or changed incompatibly and could break your application.
Please modify your code to eliminate dependency on any JDK internal APIs.
For the most recent update on JDK internal API replacements, please check:
https://wiki.openjdk.java.net/display/JDK8/Java+Dependency+Analysis+Tool

JDK Internal API                         Suggested Replacement
----------------                         ---------------------
sun.misc.BASE64Encoder                   Use java.util.Base64 @since 1.8

If you use Maven, there’s a jdeps plugin available.

For jdeps syntax, see jdeps in the Java Development Kit Tool Specifications.

Keep in mind that jdeps is a static analysis tool, and static analysis of code might not provide a complete list of dependencies. If the code uses reflection to call an internal API, then jdeps doesn’t warn you.

Migrate Away From the COMPAT Locale Data Provider

The COMPAT locale data provider, which represents the locale data that is compatible with releases prior to JDK 9, has been removed in JDK 23.

See if your applications rely on COMPAT. Do this by checking if the value COMPAT is specified in the system property java.locale.providers. If so, then test your applications with the latest JDK and the default CLDR locale provider with respect to locale-related functions, such as the formatting and parsing of dates, times, and numbers. If your tests yield unexpected results, then try one of the following options:

Force Locale-Sensitive APIs to Use Legacy Locale Data at Startup

Do this by starting the Java runtime with:
$ java -Djava.locale.providers=JRE,CLDR
The system property value COMPAT can be used as a synonym for JRE, for example, -Djava.locale.providers=COMPAT,CLDR.

Note:

Forcing the use of legacy locale data must be treated as a temporary measure. In a release after JDK 9, only CLDR locale data will be available.

Modify Your Code to Always Format and Parse Strings With the Same Patterns as Those in Legacy Locale Data

For example, suppose your code uses the locale-sensitive SimpleDateFormat API to format Date objects. On JDK 8, the code might have obtained a SimpleDateFormat as follows:
SimpleDateFormat fmt
    = (SimpleDateFormat)DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.UK);
// prints "19-Mar-2024" on JDK 8 but "19 Mar 2024" on JDK 9
System.out.println(fmt.format(new Date()));

This solution can work well for small applications, or for large applications that store formats in singleton variables whose use is rigorously enforced across the codebase.

Create a Custom Locale Data Provider and Include It in the Application

A custom locale provider can override the CLDR provider so that locale-sensitive APIs, when formatting and parsing strings, give priority to the patterns defined by the custom locale provider.

Note:

This option is complex because it involves changes to how the application is packaged and deployed. Before considering this option, investigate the previous option, Modify Your Code to Always Format and Parse Strings With the Same Patterns as Those in Legacy Locale Data. This option is more localized.
For example, here is a custom locale data provider that can be used on JDK 9 to reinstate the hyphen-separated pattern for UK dates from JDK 8:
package com.example.localization;
import java.text.*;
import java.text.spi.*;
import java.util.*;

public class HyphenatedUKDates extends DateFormatProvider {

     @Override
     public Locale[] getAvailableLocales() {
         return new Locale[]{Locale.UK};
     }

     @Override
     public DateFormat getDateInstance(int style, Locale locale) {
         assert locale.equals(Locale.UK);
         switch (style) {
             case DateFormat.FULL:
                 return new SimpleDateFormat("EEEE, d MMMM yyyy");
             case DateFormat.LONG:
                 return new SimpleDateFormat("dd MMMM yyyy");
             case DateFormat.MEDIUM:
                 return new SimpleDateFormat("dd-MMM-yyyy");
             case DateFormat.SHORT:
                 return new SimpleDateFormat("dd/MM/yy");
             default:
                 throw new IllegalArgumentException("style not supported");
         }
     }

     @Override
     public DateFormat getDateTimeInstance(int dateStyle, int timeStyle,
                                           Locale locale)
     {
         ...
     }

     @Override
     public DateFormat getTimeInstance(int style, Locale locale) {
         ...
     }

}

The following is another example. The short month name for September differs between CLDR and COMPAT in the UK locale. The following SPI implementation addresses this incompatibility:

package spi;

import java.text.DateFormatSymbols;
import java.text.spi.DateFormatSymbolsProvider;
import java.util.Locale;

public class ShortMonthModifier extends DateFormatSymbolsProvider {

    @Override
    public DateFormatSymbols getInstance(Locale locale) {
        assert locale.equals(Locale.UK);
        return new DateFormatSymbols() {
            @Override
            public String[] getShortMonths() {
                var ret = new DateFormatSymbols(Locale.UK).getShortMonths().clone();
                ret[Calendar.SEPTEMBER] = "Sep";
                return ret;
            }
        };
    }

    @Override
    public Locale[] getAvailableLocales() {
        return new Locale[]{Locale.UK};
    }
}

Once you have implemented a custom locale data provider, package it as described in the section Packaging of Locale Sensitive Service Provider Implementations in the LocaleServiceProvider JavaDoc API documentation. Afterward, place it on the classpath and then run your applications with the -Djava.locale.providers=SPI,CLDR command-line option.

See JEP 252: Use CLDR Locale Data by Default.