Java 21 まで動いていたアプリを JDK 25 で起動すると、アプリのログが 1 行も出ないまま、次のエラーで終わることがあります。
Error occurred during initialization of VM
java.lang.Error: A command line option has attempted to allow or enable the Security Manager. Enabling a Security Manager is not supported.
at java.lang.System.initPhase3(java.base@25.0.3/System.java:1970)
コピペ検索用の表記ゆれです。同じ原因が、渡し方によって別の見え方をします。
A command line option has attempted to allow or enable the Security ManagerError occurred during initialization of VM(この 1 行だけがログに残ることもあります)UnsupportedOperationException: Setting a Security Manager is not supported(JDK 24 以降・実行時)UnsupportedOperationException: The Security Manager is deprecated and will be removed in a future release(JDK 18〜23・実行時)
スタックトレースは java.lang.System.initPhase3 を指していて、あなたのコードを 1 行も指していません。それもそのはずで、この失敗は main() に到達する前に起きています。犯人はコードではなく、JVM に渡っている 1 個の起動フラグです。
直し方の要点を先に
-Djava.security.manager の指定を JVM 引数から消します。
# 落ちる
java -Djava.security.manager=allow -jar app.jar
# 起動する
java -jar app.jar
値は allow とは限りません。JDK 24 以降は、Security Manager を有効化しうる指定がすべて起動時に拒否されます。次の 5 つは、どれも同じエラーになります(JDK 24 と 25 で実測)。
| 指定 | JDK 24 / 25 |
|---|---|
-Djava.security.manager(値なし) | 起動しない |
-Djava.security.manager=(空文字) | 起動しない |
-Djava.security.manager=allow | 起動しない |
-Djava.security.manager=default | 起動しない |
-Djava.security.manager=<クラス名> | 起動しない |
-Djava.security.manager=disallow | 起動する(警告も出ない) |
=allow が圧倒的に多いはずです。これは JDK 18 で System.setSecurityManager が既定で拒否されるようになったときの定番の対処で、18 から 23 までは、これで従来どおり動きます。
フラグを消して起動するなら、それで終わりです。消したら今度は実行時に例外が出たなら、アプリが本当に Security Manager を使っています。その場合は フラグを消したあとに出る例外 へ進んでください。
なぜ 24 で起動しなくなるのか
Security Manager は JDK 17 で非推奨になり(JEP 411)、JEP 486 が JDK 24 で恒久的に無効化しました。
java.lang.SecurityManager は JDK 25 にも残っていて、参照するコードはコンパイルも実行もできます(marked for removal の警告が出るだけです)。削除されたのではなく、無くなったのは「有効化する手段」のほうです。API そのものの削除は、将来の版に予告されている段階にあります。
段階を追うと、同じ起動コマンドの結果が版で 3 回変わります。
| JDK | System.setSecurityManager を呼ぶだけ | -Djava.security.manager=allow を付ける |
|---|---|---|
| 17 | 成功する | 成功する(警告つき) |
| 18〜23 | UnsupportedOperationException(既定が拒否に変わった) | 成功する(警告つき)。これが当時の対処 |
| 24 / 25 / 26 | UnsupportedOperationException(文言が変わる) | JVM が起動しない |
18 で拒否されるようになったとき、検索して出てくる答えは「-Djava.security.manager=allow を付けろ」でした。それは 23 まで正しく動きます。そして 24 以降、その同じフラグが起動を止めます。当時「正しく対処した」チームほど、JDK を上げた瞬間に起動しないアプリを手にすることになる。
境界は LTS の切れ目に乗っています。JDK 21 には逃げ道があり、次の LTS である JDK 25 には無い。21 から 25 へ跳ぶ人が、そのまま踏みます。
フラグを消したあとに出る例外
フラグを消しても、System.setSecurityManager を呼んでいるコードが残っていれば、今度は実行時に落ちます。
Exception in thread "main" java.lang.UnsupportedOperationException: Setting a Security Manager is not supported
at java.base/java.lang.System.setSecurityManager(System.java:304)
at Deep$Lib.install(Deep.java:2)
at Deep$Framework.boot(Deep.java:3)
at Deep.main(Deep.java:4)
VM 起動エラーはあなたのコードを 1 行も指しませんでしたが、この例外は呼び出し元を名指しします。見るのは System.setSecurityManager の直下のフレームです。上の例なら Deep$Lib.install。いちばん下の行(Deep.main)はプログラムの入口で、テストランナーやフレームワークのブートストラップが並ぶだけなので、そこを直しても意味がありません。
フラグを外すことは、原因の場所を JVM に吐かせる手順でもあります。-Djava.security.manager=allow を付けたままだと、いつまでもここへ辿り着けません。
JDK 24 以降は Security Manager を有効化できず、API も無効化されています。その呼び出しは、消す前からすでに何も強制していません。 裏を返すと、Security Manager で実際に何かを守っていたなら、それは JDK 24 へ上げると決めた時点で失われています。呼び出しを消すのは、失効した残骸の後片付けであって、防御の移行ではない。何を守っていたのか(サンドボックス、System.exit の禁止、テスト中のファイル・ネットワーク遮断など)によって、置き換え先はそれぞれ別の仕組みになります。この記事では扱いません。
名指しされたのが依存ライブラリの中なら、そのライブラリの changelog で、JDK 24 以降に対応した版があるかを確認してください(どの版で解消するかは検証していません)。
なお、実行時例外の文言は 24 で変わります。
| JDK | 実行時例外のメッセージ |
|---|---|
| 18〜23 | The Security Manager is deprecated and will be removed in a future release |
| 24 以降 | Setting a Security Manager is not supported |
古い文言で検索してたどり着いた記事は、新しい文言の読者を助けません。逆も同じです。
フラグはどこに隠れているか
java コマンドに直接書かれているとは限りません。環境変数から注入されても、まったく同じ VM 起動拒否になります。
$ JAVA_TOOL_OPTIONS=-Djava.security.manager=allow java -version
Picked up JAVA_TOOL_OPTIONS: -Djava.security.manager=allow
Error occurred during initialization of VM
java.lang.Error: A command line option has attempted to allow or enable the Security Manager. ...
先頭の Picked up ... の 1 行が、注入元を教えてくれます。ただしこの行は、環境変数から来たときにしか出ません。経路と手がかりの対応は次のとおりです(JDK 25 で実測)。
| 経路 | 出る行 |
|---|---|
JAVA_TOOL_OPTIONS | Picked up JAVA_TOOL_OPTIONS: ... |
_JAVA_OPTIONS | Picked up _JAVA_OPTIONS: ... |
JDK_JAVA_OPTIONS | NOTE: Picked up JDK_JAVA_OPTIONS: ...(接頭辞が違う) |
@argfile(引数ファイル) | 何も出ない |
ラッパースクリプト・systemd の ExecStart | 何も出ない(java に直接渡っているため) |
Picked up が出ていないなら環境変数由来ではない、とまでは言えます。JVM 引数の混入ではない、とは言えません。 探す場所は、だいたい次のあたりです。
DockerfileのENV JAVA_TOOL_OPTIONS- CI の環境変数設定
java @jvm.optionsのような引数ファイル- 起動スクリプト、systemd の unit ファイル
- アプリケーションサーバの
setenv.sh相当
JAVA_OPTS という名前も広く使われていますが、これは JVM が自動で読む変数ではありません。起動スクリプトが展開して java に渡しているときだけ効きます。 設定した覚えがあるのに Picked up が出ないなら、その変数を誰が展開しているかを追ってください。
フラグを消せないときの暫定策
注入元が組織共通のベースイメージや CI テンプレートにあって、自分の権限では直せないことがあります。その場合は、値を disallow に変えると VM は起動します。
java -Djava.security.manager=disallow -jar app.jar
disallow は「Security Manager を使わない」という宣言で、JDK 18 以降の既定と同じ状態です。有効化しようとしていないので拒否されません(JDK 24 と 25 の両方で、警告も出さずに起動することを確認しました)。
これで動くのは、フラグだけが惰性で残っていて、System.setSecurityManager を誰も呼んでいない場合です。呼び出しが残っていれば、disallow を付けても実行時 UnsupportedOperationException になります(実測済み)。恒久策はフラグを消すことで、disallow はそこへ辿り着くまでの足場に留めてください。
JDK は要らなくなったフラグを 3 通りに扱う
同じ「移行で要らなくなったフラグ」でも、JDK の扱い方が 1 つではないところが、この一連のエラーを分かりにくくしています。同じフラグでも、版が 1 つ違うだけで扱いが変わります。
| JDK の扱い | 起動 | 実例 |
|---|---|---|
| 非推奨(警告して動く) | 起動する | -XX:+UseBiasedLocking(JDK 17:Option UseBiasedLocking was deprecated in version 15.0) |
| 廃止(受理して黙殺する) | 起動する。指定は効いていない | --illegal-access=permit(JDK 17)/-XX:+UseBiasedLocking(JDK 18 だけ:Ignoring option ...; support was removed in 18.0) |
| 拒否 | 起動しない | -Djava.security.manager=allow(JDK 24 以降)/-XX:+UseBiasedLocking(JDK 19 以降:Unrecognized VM option) |
上の 2 つは、警告を 1 行出しただけで JVM が起動してしまいます。「フラグは効いているはず」と思い込んだまま先へ進めるのがここで、--illegal-access=permit を足しても InaccessibleObjectException が 1 文字も変わらないのはこの状態です(詳しくは InaccessibleObjectException: module java.base does not “opens java.util” の直し方)。動いているのは JVM であって、アプリのエラーは残ったままになります。
このエラーは 3 つ目です。起動を拒むぶん気づくのは早いかわりに、エラーがコードのどこも指さないので、どこを直せばいいのかが分かりません。
なお、3 つ目の中にも 2 種類あります。Unrecognized VM option は JDK がその名前を知らない(フラグが完全に消えた)。-Djava.security.manager=allow のほうは JDK が名前を知っていて、そのうえで拒む。だから文言も別物で、切り分けの手がかりになります。
切り分け
- アプリのログが 1 行も出ない/
Error occurred during initialization of VMだけが残る →main()より前の失敗です。アプリのコードではなく JVM 引数を疑ってください。java -versionを同じ引数・同じ環境変数で実行すると、アプリ抜きで再現できます。 - 起動コマンドを見てもフラグが無い → 環境変数か引数ファイルから入っています。
Picked up ...の行が出ていれば環境変数、出ていなければ引数ファイルかラッパースクリプトを疑ってください。 - フラグを消したら、今度は
UnsupportedOperationException→ 想定どおりです。アプリか依存ライブラリが実際にSystem.setSecurityManagerを呼んでいます。System.setSecurityManagerの直下のフレームが呼び出し元です。 - JDK 21 では同じ設定で動いていた → 21 は
=allowを受け付ける最後の LTS です。24 で拒否に変わりました。 - エラーが
Unrecognized VM option→ 別のフラグの話です。そちらは JDK が名前ごと知らないオプションで、Security Manager とは無関係に起動が失敗しています。 --illegal-access=permitを足しても例外が変わらない → 別件です。InaccessibleObjectException の記事を参照してください。UnsupportedClassVersionErrorが出る → こちらはクラスファイルの版の話です。has been compiled by a more recent version of the Java Runtime の直し方へ。
検証環境
eclipse-temurin@sha256:68868d04...(openjdk version "25.0.3" LTS)、ネットワーク無し- 再現:
System.setSecurityManager(new SecurityManager())を含むクラスを-Djava.security.manager=allow付きで実行し、A command line option has attempted to allow or enable the Security Managerが出て終了コード 1 - 修正:フラグと
setSecurityManagerの呼び出しを両方外して終了コード 0・シグネチャ消滅。フラグだけ外した状態も実行し、実行時UnsupportedOperationExceptionに変わることを確認しています(=フラグを外すだけでは直りません) - 値の表(値なし/空文字/
allow/default/クラス名/disallow)は、JDK 24 と 25 の両方で 6 通りとも実行しました disallowを付けたうえでsetSecurityManagerを呼ぶと実行時例外になること、3 層の呼び出しでスタックトレースの直下のフレームが呼び出し元になることも、個別に実行して確認しています- 注入経路の表(
JAVA_TOOL_OPTIONS/_JAVA_OPTIONS/JDK_JAVA_OPTIONS/@argfile)も同じイメージで実測しました java.lang.SecurityManagerが JDK 25 で解決でき、System.getSecurityManager()がnullを返すこと(=削除ではない)も確認済みです- 版の表(17 / 18〜23 / 24 / 25 / 26)は
eclipse-temurinの 10 版でそれぞれ実行して確認しました --illegal-access=permitが受理・黙殺されることは、姉妹記事のケース(JDK 17)で確認済みです
Maven Surefire の argLine や Gradle の jvmArgs からフラグが渡る経路は、機構が同じ(JVM に引数が届くか)ことを根拠にした説明であり、ビルドツール経由での再現は通していません。どのフレームワークやライブラリが -Djava.security.manager=allow を要求していたかも確認していません。