Javaのリフレクションは、実行時にクラスのメソッド・フィールド・コンストラクタを java.lang.reflect のオブジェクトとして取り出し、名前を文字列で指定して呼び出したり書き換えたりする仕組みです。SpringのDI、JUnitのテスト検出、Jacksonのシリアライズが内部で使っています。この記事では、JDK 25(LTS)と2026年9月15日にGAとなったJDK 27で実際に動かしたコードで、Method.invoke、アノテーションの読み取り、動的プロキシを示し、Java 17以降に「禁止」される操作と代替手段の選び方までを扱います。言語をまたいだ概念の整理はリフレクションとは?実行時にクラス情報を操る仕組みを言語横断で解説にまとめています。
まとめ:Javaリフレクションの使い方と制限の要点
- 起点は
Classオブジェクトです。Class.forName("p.Account")で取得し、getDeclaredMethod・getDeclaredField・getDeclaredConstructorで部品を取り出します。 getMethodは継承分を含むpublicメンバーだけ、getDeclaredMethodはそのクラス自身が宣言したメンバーをprivateも含めて返します。- 別のネストに属するクラスのprivateメンバーは
setAccessible(true)でアクセス検査を外してから使います。呼び出し先が投げた例外はInvocationTargetExceptionに包まれるので、getCause()で取り出します。 - アノテーションを実行時に読めるのは
@Retention(RetentionPolicy.RUNTIME)が付いたものだけです。 java.lang.reflect.Proxyはインタフェースにだけ割り込めます。ハンドラーから投げたInvocationTargetExceptionがインタフェースメソッドのthrows宣言で許容されていない場合、呼び出し側にはUndeclaredThrowableExceptionが届きます。- Java 17以降、呼び出し元に開かれていないJDKのパッケージ内のprivateメンバーに対する
setAccessible(true)はInaccessibleObjectExceptionで失敗します。finalフィールドの書き換えはJDK 26から警告が出て、JDK 27でも同じです。 - 型安全を失うため、アプリケーションのコードで常用するものではありません。インタフェース、MethodHandle、アノテーションプロセッサで置き換えられるならそちらを選びます。
Javaリフレクションの仕組みとjava.lang.reflectの主要クラス
JVMはロードしたクラスごとに java.lang.Class のインスタンスを1つ持ち、そこからメンバーの情報をたどれます。リフレクションはこの Class を入口に、メンバーを表すオブジェクトを受け取って操作するAPIです。
| クラス | 表すもの | 主な操作 |
|---|---|---|
| Class | クラス・インタフェース・record | forName / getDeclaredFields |
| Method | メソッド | invoke / getParameterTypes |
| Field | フィールド | get / set / getGenericType |
| Constructor | コンストラクタ | newInstance |
| Proxy | 動的プロキシ | newProxyInstance |
Class オブジェクトは Account.class、obj.getClass()、Class.forName("p.Account") の3通りで取れます。リフレクションが本領を発揮するのは、文字列でしか型名が分からない3つ目の場面です。
getMethodとgetDeclaredMethodの違い
名前が似ていますが、返す範囲が違います。取り違えると NoSuchMethodException になります。
| メソッド | private | 親クラスから継承したもの |
|---|---|---|
| getMethod / getMethods | 含まない | 含む(publicのみ) |
| getDeclaredMethod / getDeclaredMethods | 含む | 含まない |
親クラスに定義されたpublicメソッド hello() を子クラスの Class から探すと、getMethod("hello") では見つかり、getDeclaredMethod("hello") では NoSuchMethodException になります。親のprivateメソッドが要るときは getSuperclass() で親の Class に移ってから getDeclaredMethod を呼びます。getDeclaredMethods() や getDeclaredFields() が返す配列の順序はJavadocで保証されていないので、順番に依存したコードは書かないでください。
フィールド一覧と修飾子・ジェネリクス型の調べ方
クラスの構造を一覧するなら getDeclaredFields() を回します。修飾子の表示に長く使われてきた Modifier.toString(f.getModifiers()) は、JDK 27で非推奨になりました。JDK 27のModifierのJavadocは、クラスファイルのアクセスフラグとJava言語の修飾子の対応がずれてきたことを理由に挙げ、フラグは AccessFlag、言語上の修飾子は各オブジェクトの toGenericString() で調べるよう案内しています。JDK 27の javac -Xlint:deprecation では [deprecation] toString(int) in Modifier has been deprecated と警告されます。
for (Field f : Class.forName("p.Account").getDeclaredFields()) {
System.out.println(f.toGenericString() + " " + f.accessFlags());
}
private java.lang.String p.Account.owner [PRIVATE]
private final int p.Account.limit [PRIVATE, FINAL]
private final int p.Account.quota [PRIVATE, FINAL]
accessFlags() はJDK 20で追加されたメソッドで、JDK 25でも27でも同じ出力でした。List<String> 型のフィールドでは、getType() が interface java.util.List を、getGenericType() が java.util.List<java.lang.String> を返します。型引数まで要るJSONマッパーのような処理は getGenericType() を使います。
リフレクションの使い方:インスタンス生成からMethod.invokeまで
別パッケージの次のクラスを、リフレクションだけで生成・呼び出し・書き換えする例です。1つ目を p/Account.java、2つ目を Main.java に保存し、親ディレクトリで javac p/Account.java Main.java、続いて java -cp . Main を実行します。
package p;
public class Account {
private String owner = "taro";
private final int limit = 100;
private final int quota;
public Account() { this.quota = 10; }
private String mask(String s) { return s.charAt(0) + "***"; }
public int limit() { return limit; }
public int quota() { return quota; }
}
import java.lang.reflect.*;
public class Main {
public static void main(String[] args) throws Exception {
Class<?> c = Class.forName("p.Account");
Object a = c.getDeclaredConstructor().newInstance();
Method mask = c.getDeclaredMethod("mask", String.class);
try {
mask.invoke(a, "taro");
} catch (IllegalAccessException e) {
System.out.println("IAE: " + e.getMessage());
}
mask.setAccessible(true);
System.out.println(mask.invoke(a, "taro"));
Field owner = c.getDeclaredField("owner");
owner.setAccessible(true);
owner.set(a, "jiro");
System.out.println(owner.get(a));
}
}
JDK 25.0.4.1(Temurin 25.0.4.1+1)での出力は次のとおりです。
IAE: class Main cannot access a member of class p.Account with modifiers "private"
t***
jiro
JDK 27(Zulu 27.28、ビルド27+35)では1行目が ... cannot access a member of class p.Account with private access に変わりました。例外の型は同じ IllegalAccessException ですが、ログをメッセージの文字列で検索・集計している場合は、JDK 27以降で一致しなくなります。
Class.forNameとgetDeclaredConstructorによるインスタンス生成
引数なしなら c.getDeclaredConstructor().newInstance()、String引数のコンストラクタなら c.getDeclaredConstructor(String.class).newInstance("hanako") のように、パラメータの型で目的のコンストラクタを指定します。以前よく使われた c.newInstance() はJava 9で非推奨になりました。コンストラクタが投げたチェック例外を宣言なしに素通しするためで、Constructor.newInstance() なら InvocationTargetException に包まれ、呼び出し側で扱えます。
Method.invokeでprivateメソッドを呼ぶ手順とsetAccessible
invoke の第1引数は対象インスタンス、以降が実引数です。staticメソッドなら第1引数は null にします。上の出力のとおり、privateメソッドは setAccessible(true) を呼ぶまで IllegalAccessException で拒否されます。
呼び出し側と対象が同じトップレベルクラスの中にある(ネストしたクラス同士の)場合は、setAccessible なしでもprivateメソッドを呼べます。Java 11のJEP 181(ネストメイト)で、同じネストのメンバーは互いのprivateにアクセスできるようになったためです。内部クラスで試すと「setAccessibleは不要」と誤解しやすいので、検証は別パッケージのクラスで行ってください。
Field.setで値を書き換えるときの型と定数の落とし穴
Field.set は Object を受け取るので、型が合わなければ実行時に IllegalArgumentException になります。プリミティブ型には setInt などの専用メソッドがあります。
通常のクラスの非staticなfinalフィールドは、setAccessible(true) の後なら書き換えられます(JDK 26以降は警告付き)。static finalフィールド、recordのfinalフィールド、hidden classのfinalフィールドは書き換えられません。上の Main.main の末尾に次のコードを足し、limit(宣言時に定数 100 で初期化)と quota(コンストラクタで代入)を書き換えます。
Field lim = c.getDeclaredField("limit");
lim.setAccessible(true);
lim.setInt(a, 999);
Field q = c.getDeclaredField("quota");
q.setAccessible(true);
q.setInt(a, 50);
p.Account acc = (p.Account) a;
System.out.println("limit via field=" + lim.getInt(a) + " via method=" + acc.limit());
System.out.println("quota via field=" + q.getInt(a) + " via method=" + acc.quota());
limit via field=999 via method=100
quota via field=50 via method=50
JDK 25・26・27のいずれも同じ結果です。limit は定数式で初期化されたfinalフィールドなので、limit() の中身がコンパイル時に 100 へ置き換わっています。テストで設定値を差し替えようとして効かないときは、この定数の畳み込みを疑ってください。
アノテーションをリフレクションで読み取る手順
SpringやJPAが @Autowired や @Column を解釈できるのは、実行時にリフレクションでアノテーションを読んでいるからです。自作のアノテーションでも同じことができます。次の例は、@Column を付けたフィールドだけを列名と値の組にします。Anno.java に保存し、javac Anno.java、java Anno の順で実行します。
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
public class Anno {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Column { String value(); }
@Target(ElementType.FIELD)
@interface NoRuntime { }
static class User {
@Column("user_name") String name = "taro";
@Column("age") int age = 30;
@NoRuntime String memo = "x";
}
public static void main(String[] args) throws Exception {
User u = new User();
Map<String, Object> row = new LinkedHashMap<>();
for (Field f : User.class.getDeclaredFields()) {
Column col = f.getAnnotation(Column.class);
if (col != null) row.put(col.value(), f.get(u));
}
System.out.println(row);
Field memo = User.class.getDeclaredField("memo");
System.out.println("NoRuntime=" + memo.getAnnotation(NoRuntime.class));
}
}
{user_name=taro, age=30}
NoRuntime=null
@NoRuntime は付いているのに null が返ります。@Retention を省略した既定は CLASS で、RetentionPolicyのJavadocのとおりクラスファイルには残ってもVMは実行時に保持しません。自作アノテーションが読めないときに最初に確かめるのはここです。Lombokとは?読み方・使い方とアノテーション一覧、Maven・Gradleの設定【1.18.48】で扱う @Getter も、ソースコードで @Retention(RetentionPolicy.SOURCE) と宣言されているため、実行時のリフレクションでは見えません。Lombokはコンパイル時のアノテーションプロセッサとして動く道具です。
クラスに付けたアノテーションは、@Inherited を付けて宣言すると子クラスから getAnnotation で見えます。getDeclaredAnnotation はそのクラスに直接付いたものだけを返すので、同じ子クラスでも null になります(JDK 27で確認)。Springでのアノテーションの使われ方はSpring Bootのアノテーション一覧|仕組みと使い分け・Boot 4での変更点を参照してください。
動的プロキシ(java.lang.reflect.Proxy)による呼び出しの制御
Proxy.newProxyInstance は、指定したインタフェースを実装するクラスを実行時に生成し、そのインタフェースのメソッド呼び出しを InvocationHandler に渡します。Objectのequals・hashCode・toStringも対象ですが、getClass・waitなどは対象外です。ログ出力やトランザクションのような横断処理を、対象クラスを変えずに差し込めます。次の例を Proxy2.java に保存し、javac Proxy2.java、java Proxy2 の順で実行します。
import java.lang.reflect.*;
public class Proxy2 {
interface Greeter { String greet(String name); }
static class RealGreeter implements Greeter {
public String greet(String name) {
if (name.isEmpty()) throw new IllegalArgumentException("empty name");
return "Hello, " + name;
}
}
public static void main(String[] args) {
Greeter target = new RealGreeter();
InvocationHandler logging = (proxy, method, a) -> {
System.out.println("call " + method.getName());
try {
return method.invoke(target, a);
} catch (InvocationTargetException e) {
throw e.getCause();
}
};
Greeter g = (Greeter) Proxy.newProxyInstance(
Greeter.class.getClassLoader(), new Class<?>[] { Greeter.class }, logging);
System.out.println(g.greet("taro"));
try { g.greet(""); } catch (RuntimeException e) { System.out.println("unwrapped: " + e); }
InvocationHandler noUnwrap = (proxy, method, a) -> method.invoke(target, a);
Greeter g2 = (Greeter) Proxy.newProxyInstance(
Greeter.class.getClassLoader(), new Class<?>[] { Greeter.class }, noUnwrap);
try { g2.greet(""); } catch (RuntimeException e) { System.out.println("as is: " + e); }
}
}
call greet
Hello, taro
call greet
unwrapped: java.lang.IllegalArgumentException: empty name
as is: java.lang.reflect.UndeclaredThrowableException
JDK 25と27で同じ出力です。catch で剥がさない noUnwrap では、元の IllegalArgumentException ではなく UndeclaredThrowableException が届きます。invoke が投げる InvocationTargetException はチェック例外で、greet の throws 宣言に含まれないためです。ProxyのJavadocも、宣言されていないチェック例外はこの例外に包むと定めています。プロキシを自作するときは、ハンドラーで getCause() を投げ直す形を定型にしてください。
インタフェースではなくクラス(RealGreeter.class)を渡すと、IllegalArgumentException: Proxy2$RealGreeter is not an interface で失敗します。クラスそのものに割り込むにはサブクラスを生成するライブラリが要ります。Spring Frameworkのリファレンスによれば、Spring AOPは対象がインタフェースを1つ以上実装していればJDKの動的プロキシを、実装していなければCGLIBでサブクラスを作ります。CGLIBではfinalクラスをプロキシ化できず、finalメソッドやprivateメソッドにもアドバイスを適用できません。一方、JDKの動的プロキシなら、対象の実装クラスがfinalでもインタフェース経由の呼び出しにアドバイスを適用できます。仕組みの全体はAOP(アスペクト指向プログラミング)とは?Spring AOPの仕組みと使い方・自己呼び出しの落とし穴で解説しています。
InvocationTargetExceptionの原因と例外処理
java.lang.reflect.InvocationTargetException は、invoke や newInstance で呼んだ先のメソッド自身が例外を投げたときに、その例外を包んで投げ直すチェック例外です。リフレクションの呼び出しは成功していて、中で例外が起きた、という意味になります。
try {
mask.invoke(a, "");
} catch (InvocationTargetException e) {
System.out.println("msg=" + e.getMessage());
System.out.println("cause=" + e.getCause());
}
msg=null
cause=java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
上の出力のとおりメッセージは null なので、e.getMessage() だけをログに出すと原因が消えます。e.getCause() を出すか例外オブジェクトごとロガーに渡し、スタックトレースでは「Caused by:」の行を見てください。元の例外として扱いたいなら、getCause() が RuntimeException や Error のときはそのまま投げ直します。
| 例外 | 起きる場面 |
|---|---|
| ClassNotFoundException | forNameのクラス名誤り・クラスパス不足 |
| NoSuchMethodException | 名前か引数の型が違う |
| IllegalAccessException | setAccessible前のprivateアクセス |
| InvocationTargetException | 呼び出した先で例外発生 |
| InaccessibleObjectException | モジュールが開いていない |
| UndeclaredThrowableException | プロキシが未宣言のチェック例外を投げた |
リフレクションが禁止される理由とJava 17以降の制限
「リフレクションは禁止」には2種類あります。型安全とリファクタリング耐性を失うためにアプリケーションコードでの使用を禁じるコーディング規約と、JDKそのものによる制限です。後者はJavaを上げると既存コードが実際に動かなくなります。
JDK内部へのアクセスとInaccessibleObjectException(JEP 403)
Java 9のモジュールシステム以降、JDK内部への深いリフレクションは段階的に閉じられ、JDK 17のJEP 403で --illegal-access オプションによる一括の抜け道がなくなりました。String のprivateフィールドを開こうとすると、JDK 25・26・27のいずれでも次の例外になります。
java.lang.reflect.InaccessibleObjectException: Unable to make field private final byte[] java.lang.String.value accessible: module java.base does not "opens java.lang" to unnamed module @7fbe847c
ライブラリの更新で解消できないときの応急処置は、java --add-opens java.base/java.lang=ALL-UNNAMED Main のように該当パッケージだけを開くことです(同じコードが通ることを確認済み)。ただしJDK内部の実装に依存し続けるので、JDKを上げるたびに壊れる可能性が残ります。Java 8から上げてこの例外が出たら、まず原因のライブラリを対応版に更新します。移行全体の進め方はJava近代化の進め方|Java 8から25へ上げる手順と実機で落ちる箇所【2026年10月】で扱っています。
finalフィールドの書き換え警告(JEP 500・JDK 26と27)
JDK 26のJEP 500で、リフレクションによるfinalフィールドの書き換えに警告が出るようになりました。前の章で Account.limit を書き換えるコードをJDK 26.0.2.1で動かすと、処理は続行し、次の警告が出ます(JDK 27でも同じ)。
WARNING: Final field limit in class p.Account has been mutated reflectively by class Main in unnamed module @33a10788 (file:/private/tmp/refl3255/out/)
WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning
WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled
既定は --illegal-final-field-mutation=warn で、警告はモジュールごとに最初の1回だけ出ます。--illegal-final-field-mutation=deny を付けて起動すると、将来の既定である拒否の挙動をいま試せます(JDK 25にはこのオプションが無く、起動時に Unrecognized option で止まります)。
java.lang.IllegalAccessException: class Main (in unnamed module @7bb11784) cannot set final field p.Account.limit (in unnamed module @7bb11784), unnamed module @7bb11784 is not allowed to mutate final fields
2026年10月22日更新のJDK 28のページを見る限り、拒否を既定にするJEPはまだ対象に入っていません。それでも、テストやDIでfinalフィールドに値を注入している箇所は、この警告を目印に今のうちに洗い出しておくべきです。根本対応はコンストラクタ注入への変更で、--enable-final-field-mutation での許可は移行までのつなぎです。JDK 26の変更点はJava 26(JDK 26)の新機能10個のJEPと現在地|サポート終了後の移行先と実機で確かめた挙動、JDK 27はJava 27とは|非LTSの半年運用とG1・オブジェクトヘッダ既定化の影響にまとめています。
recordフィールドの書き換え禁止とSecurityManagerの恒久無効化
recordの構成要素に対応するfinalなインスタンスフィールドは setAccessible(true) の後でも書き換えられません。JEP 500によれば、JDK 16でrecordを導入した時点から深いリフレクションによる書き換えを禁じています。record Point(int x, int y) の x に Field.setInt を呼ぶと、Can not set final int field Demo$Point.x to (int)5 の IllegalAccessException になります。構成要素の名前や型は getRecordComponents() で取得できます。
かつてリフレクションの権限を絞る手段だったSecurityManagerは、JDK 24のJEP 486で恒久的に無効化されました。ReflectPermission("suppressAccessChecks") でアプリ内の setAccessible を止める設計は、現行のJDKでは成り立ちません。制限の手段はモジュールの opens と、上のfinal書き換えの制御です。
SpringやJUnitがリフレクションを使っている箇所
- Spring:
@Autowiredのフィールド注入は、privateフィールドにsetAccessible(true)して値を入れる処理です。@Transactionalなどは前の章の動的プロキシかCGLIBのサブクラスで実現されています。 - JUnit:
@Testが付いたメソッドを走査で見つけ、invokeで実行します。テストが投げた例外がレポートで元の型のまま見えるのは、InvocationTargetExceptionを内部で剥がしているためです。書き方はJUnitとは?Javaの単体テストの書き方・実行結果の読み方とJUnit 6の変更点で扱っています。 - Jackson:JSONのキー名とフィールド・getterの名前を実行時に突き合わせます。任意のクラス名をJSONから受け取ってインスタンス化する設定は、安全でないデシリアライゼーションとは?RCEに至る仕組みと言語別の防御実装を解説で説明している攻撃の入口になります。
リフレクションを使うべき場面と代替手段の選び方
自前のコードでリフレクションを書いてよいのは、呼び出す型がコンパイル時に本当に決まらない場合だけです。プラグインのjarを実行時に読み込む、設定ファイルに書かれたクラス名から処理を切り替える、といった場面が該当します。「if文が長くなるから」「privateを触りたいから」という理由なら、次の代替を先に検討してください。業務システムでこの線引きを設計段階から決めておきたい場合は、フルスクラッチ開発の支援でもご相談いただけます。
| やりたいこと | リフレクション以外の手段 |
|---|---|
| 処理を動的に切り替える | インタフェース+Map、ServiceLoader |
| 同じメソッドを繰り返し呼ぶ | MethodHandle |
| アノテーションからコードを作る | アノテーションプロセッサ |
| privateをテストしたい | 公開メソッド経由のテスト |
MethodHandleに置き換える条件
java.lang.invoke.MethodHandle は、アクセス権の検査を取得時に1回だけ行い、以降の呼び出しでは省けるAPIです。privateメソッドも MethodHandles.privateLookupIn(Account.class, MethodHandles.lookup()) から findVirtual で取得できます(別モジュールのクラスなら対象パッケージの opens が前提)。JDK 18のJEP 416で Method.invoke 自体もMethodHandleの上に作り直され、Method などを static final のフィールドに保持するとJITが定数として最適化できるようになりました。呼び出しのたびに getDeclaredMethod で探し直すコードはこの恩恵を受けられません。まず取得結果を static final に置き、それでも足りない箇所だけMethodHandleへ移します。
GraalVMネイティブイメージで失敗する理由
GraalVMのネイティブイメージはビルド時に到達可能なコードだけを取り込みます。Class.forName("Foo") のような定数の呼び出しは自動で解析されますが、設定ファイルや入力で決まるクラス名は実行時に見つかりません。そうした型は META-INF/native-image/ 配下の reachability-metadata.json で登録します。Spring Boot 3以降のAOT処理がリフレクション用のメタデータを自動生成しているのはこのためです。採用の可能性があるなら自前のリフレクションは避けておきます。全体像はGraalVMとは?JVMとの違いとNative Imageの作り方【2026年版】を参照してください。
privateメソッドをリフレクションでテストしない理由
privateメソッドを setAccessible(true) で直接呼ぶテストは、名前を変えてもコンパイルエラーにならず、実行時の NoSuchMethodException で初めて気づくことになります。privateのロジックが単独でテストしたいほど大きいなら、責務を別クラスに切り出します。同じパッケージからテストできるなら、公開範囲はpackage-privateにとどめ、外部への公開が必要な場合だけpublicにします。外部依存を差し替えたいだけなら、テストダブル(TestDouble)とは|5分類の違いと使い分けで紹介しているスタブやモックを使います。
よくある質問
Javaのリフレクションとは何ですか?
実行時にクラスのメソッド・フィールド・コンストラクタを java.lang.reflect のオブジェクトとして取得し、名前を指定して呼び出したり書き換えたりする仕組みです。コンパイル時に型が決まらない処理に使います。
リフレクションはなぜ禁止と言われるのですか?
型の誤りがコンパイル時に見つからず、名前の変更で実行時に壊れるため、コーディング規約で禁じる現場があります。JDK側でも、Java 17以降は、呼び出し元に開かれていないJDKのパッケージ内のprivateメンバーに対する setAccessible(true) が InaccessibleObjectException で失敗し、JDK 26からはfinalフィールドの書き換えに警告が出ます。
Javaでリフレクションを禁止するにはどうすればよいですか?
SecurityManagerはJDK 24で無効化されたので、実行時に一律で止める手段はありません。自分のコードを守るならモジュール(module-info.java)で必要なパッケージだけを opens し、JDK 26以降では --illegal-final-field-mutation=deny で、許可されていないfinalフィールドのリフレクションによる書き換えを拒否できます。--enable-final-field-mutation で許可したモジュールは対象外です。チームで使用を禁じるなら、Checkstyleの IllegalImport などで java.lang.reflect のimportを検出します。
method.invoke()でメソッドを動的に呼び出すには?
Class から getDeclaredMethod("名前", 引数の型...) で Method を取り、method.invoke(インスタンス, 引数...) を呼びます。別のネストにあるprivateメソッドは、先に setAccessible(true) でアクセス検査を外します。staticメソッドは第1引数に null を渡します。
InvocationTargetExceptionが出たときの原因の調べ方は?
呼び出した先のメソッドが例外を投げています。e.getCause() で元の例外を取り出すか、スタックトレースの「Caused by:」を確認してください。invoke が投げたこの例外の e.getMessage() は null です。