6 移行の準備

ヒント:

Java移行分析を使用した移行作業の評価

Oracleは、Java SEサブスクライバおよびOracle Cloud Infrastructure (OCI)ユーザーにJava Management ServiceのJava移行分析機能を提供します。この機能により、新しいJDKバージョンへの移行に関連する労力の包括的な分析が実行されます。これによって、必要な労力や、古いJDKバージョンから新しいバージョンに移行するための変更が必要な領域を強調した詳細なレポートが生成されます。移行プロセスでは、移行分析の分析レポートを利用して、変更が必要なクラスとAPIを特定できます。レポートでは、変更が必要なソース・コードの行番号を指定し、必須の変更と推奨変更の両方を強調します。

次の項は、アプリケーションの移行を成功させるのに役立ちます。

最新のJDKのダウンロード

Java SEダウンロードから最新のJDKリリースをダウンロードしてインストールします。

再コンパイルする前のプログラムの実行

最新のJDKリリース(JDK 24)でアプリケーションを実行します。たいていのコードおよびライブラリは変更しなくてもJDK 24上で動作するはずですが、一部のライブラリはアップグレードの必要があります。

ノート:

移行は反復的なプロセスです。プログラムをまず実行してみてから(このタスク)、これらの3つのタスクを平行して進めるのがおそらく最善でしょう:

アプリケーションの実行時に、廃止されたVMオプションに関するJVMからの警告がないか確認します。VMの起動に失敗する場合は、「削除されたGCオプション」を確認してください。

アプリケーションが正常に起動する場合は、慎重にテストして動作が使用中のJDKバージョンのときと同じであることを確認します。たとえば、一部の早期導入者は日付および通貨の形式が違っていることに気付いてきました。「COMPATロケール・データ・プロバイダからの移行」を参照してください。

最新のJDKリリースでコードを動作させるには、各JDKリリースの新機能および変更点を確認します。「JDKにおける重要な変更」を参照してください。

プログラムが正常に動作しているようであっても、このガイドの残りのステップを完了して問題のリストを確認するようにしてください。

サードパーティ・ライブラリの更新

使用するすべてのツールおよびサードパーティ・ライブラリについて、最新のJDKリリースをサポートする更新版が必要となる可能性があります。

最新のJDKで動作するように設計された各ライブラリまたはツールのバージョンについては、サードパーティ・ライブラリおよびツール・ベンダーのWebサイトを確認してください。提供されている場合は、その新しいバージョンをダウンロードおよびインストールしてください。

MavenまたはGradleを使用してアプリケーションを構築している場合は、最新のJDKバージョンをサポートしている最近のバージョンにアップグレードしてください。

IDEを使用してアプリケーションを開発している場合は、既存のコードを移行するのに役立ちます。NetBeans、EclipseおよびIntelliJのIDEはいずれも、最新のJDKのサポートを含むバージョンが提供されています。

OpenJDK Wikiの品質支援に関する項で、多くのフリー・オープン・ソース・ソフトウェア(FOSS)プロジェクトをテストしたステータスを確認できます。

アプリケーションのコンパイル(必要に応じて)

問題があることがわかっているAPIや機能にコードが依存している可能性があるため、最新のJDKコンパイラでコードをコンパイルすると、将来のリリースへの移行が容易になります。ただし、必ず必要というわけではありません。

コードをJDK 11以降のコンパイラでコンパイルする必要がある場合、次の点に注意してください。

  • -sourceおよび-targetオプションのかわりに新しい--releaseフラグを使用します。Java Development Kitツール仕様javacに関する項を参照してください。

    サポートされている--releaseの値は、現在のJava SEリリースと、コマンドライン・ヘルプで詳しく説明されている、限られた数の以前のリリースです。

    javacは、JDK 1.0.2クラス・ファイルまでさかのぼる、以前のすべてのJDKのクラス・ファイルを認識および処理できます。

    『JEP 182: Policy for Retiring javac -source and -target Options』を参照してください。

  • javac-sourceおよび-targetオプションを使用する場合、使用する値を確認してください。

    サポートされる-source/-target値は、9までは24 (デフォルト)です。値8は非推奨であり、警告が発生します。

    JDK 8では、-sourceおよび-targetの値に1.7/7以前の値を指定するのは非推奨となり、警告が表示されました。JDK 9以降では、これらの値はエラーになります。

    >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.

    ノート:

    --releaseを使用する場合は、--source/-sourceまたは--target/-targetオプションも使用できません。
  • ソース・コードでアンダースコア文字(_)を1文字で識別子として使用している場合、JDK 11以降のリリースではそのコードはコンパイルできません。このように使用するとJDK 8では警告となり、JDK 9以降ではエラーとなります。

    次に例を示します:

    static Object _ = new Object();

    このコードではコンパイラにより次のエラー・メッセージが生成されます。

    MyClass.java:2: error: as of release 9, '_' is a keyword, and may not be used as a legal identifier.
    
  • sun.misc.Unsafeなどの重要な内部JDK APIにはJDK 11以降でも引き続きアクセスできますが、JDKの大部分の内部APIにはコンパイル時にはアクセスできません。アプリケーションまたはそのライブラリが内部APIに依存していることを示すコンパイル・エラーが発生することがあります。

    依存関係を特定するには、Java依存性分析ツールを実行します。「コードに対するjdepsの実行」を参照してください。可能であれば、サポートされている代替APIを使用するようにコードを更新してください。

    JDK内部クラスへの参照があるソース・コードをコンパイルするための一時的な回避策として、--add-exportsおよび--add-opensオプションを使用できます。これらのオプションの詳細は、JEP 261: モジュール・システムに関する項および「JDK内の強力なカプセル化」を参照してください。

  • 以前より多くの非推奨メッセージが表示される可能性があります。

コードに対するjdepsの実行

アプリケーションに対してjdepsツールを実行して、アプリケーションおよびライブラリが依存するパッケージおよびクラスを確認します。内部APIを使用している場合、jdepsにより、コードを更新する際に役立つ修正候補が表示されることがあります。

内部JDK APIの依存性を検出するには、jdeps-jdkinternalsオプションとともに実行します。たとえば、sun.misc.BASE64Encoderを呼び出すクラスに対してjdepsを実行すると、次のように表示されます。

>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

Mavenを使用している場合、利用可能なjdepsプラグインがあります。

jdepsの構文は、Java Development Kitツール仕様jdepsに関する項を参照してください。

jdepsは静的分析ツールであり、コードの静的分析では依存性の完全なリストが得られない可能性があることに注意してください。コードでリフレクションを使用して内部APIが呼び出されている場合、jdepsでは警告は表示されません。

COMPATロケール・データ・プロバイダからの移行

JDK 9より前のリリースと互換性のあるロケール・データを表すCOMPATロケール・データ・プロバイダは、JDK 23で削除されました。

アプリケーションがCOMPATに依存しているかどうかを確認します。これを行うには、システム・プロパティjava.locale.providersで値COMPATが指定されているかどうかを確認します。その場合、日付、時間および数値の書式設定や解析などのロケール関連関数に関して、最新のJDKおよびデフォルトのCLDRロケール・プロバイダでアプリケーションをテストします。テストで予期しない結果が発生した場合は、次のいずれかのオプションを試行してください:

起動時にロケール依存APIがレガシー・ロケール・データを使用するように強制

これを行うには、次を使用してJavaランタイムを起動します:
$ java -Djava.locale.providers=JRE,CLDR
システム・プロパティ値のCOMPATは、JREのシノニムとして使用できます(たとえば、-Djava.locale.providers=COMPAT,CLDR)。

ノート:

レガシー・ロケール・データの使用の強制は、一時的な手段として扱う必要があります。JDK 9より後のリリースでは、CLDRロケール・データのみが使用可能になります。

レガシー・ロケール・データと同じパターンを持つ文字列を常に書式設定および解析するようにコードを変更

たとえば、コードでロケール依存のSimpleDateFormat APIを使用して、Dateオブジェクトを書式設定するとします。JDK 8では、コードは次のようにSimpleDateFormatを取得している可能性があります:
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()));

このソリューションは、小規模なアプリケーションや、コードベース全体で使用が厳密に強制されるシングルトン変数に書式設定を格納する大規模なアプリケーションで適切に動作します。

カスタム・ロケール・データ・プロバイダの作成およびアプリケーションへの組込み

カスタム・ロケール・プロバイダは、CLDRプロバイダをオーバーライドして、ロケール依存のAPIが、文字列の書式設定および解析時にカスタム・ロケール・プロバイダによって定義されたパターンを優先するようにできます。

ノート:

このオプションは、アプリケーションをパッケージ化してデプロイする方法の変更を伴うため、複雑です。このオプションを検討する前に、前述のオプション「レガシー・ロケール・データと同じパターンを持つ文字列を常に書式設定および解析するようにコードを変更」を調査してください。このオプションはよりローカライズされています。
たとえば、JDK 8のUK日付のハイフン区切りパターンを回復するためにJDK 9で使用できるカスタム・ロケール・データ・プロバイダを次に示します:
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) {
         ...
     }

}

次に、別の例を示します。たとえば、9月の月の短縮名は、UKロケールではCLDRCOMPATで異なります。次のSPI実装は、この非互換性に対処します:

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};
    }
}

カスタム・ロケール・データ・プロバイダを実装したら、LocaleServiceProvider JavaDoc APIドキュメントのロケールに依存するサービス・プロバイダ実装のパッケージ化に関する項の説明に従ってパッケージ化します。その後、クラスパスに配置し、-Djava.locale.providers=SPI,CLDRコマンドライン・オプションを指定してアプリケーションを実行します。

JEP 252: デフォルトでのCLDRロケール・データの使用に関する項を参照してください。