Error [cannot find symbol]

Lombok の getter が cannot find symbol になる(JDK 23 以降は注釈処理が既定で走らない)

FIX SUMMARY verified
Applies when
eclipse-temurin@sha256:68868d04fa9cfd5f5c6abec0b5cef86d8de2bf9c62c37c7d3e4f0f80f5cfd7ffjavac annotation processing default policy (JDK 23 stopped running class-path-discovered processors implicitly) + Lombok 1.18.x + maven-compiler-plugin 3.13.0upgrading the build JDK to 23 or later with Lombok declared only as a normal compile-scope dependency — the processor never starts, so every call to a generated accessor fails with cannot find symbol and nothing in the error names Lombok

Verified: reproduced in eclipse-temurin@sha256:68868d04fa9cfd5f5c6abec0b5cef86d8de2bf9c62c37c7d3e4f0f80f5cfd7ff, then the cannot find symbol signature was gone after the fix (exit 0).

@Getter を付けたフィールドの getter が、コンパイルの時点で存在しないものとして扱われます。

[INFO] --- compiler:3.13.0:compile (default-compile) @ demo ---
[INFO] Compiling 1 source file with javac [debug release 21] to target/classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR : 
[INFO] -------------------------------------------------------------
[ERROR] src/main/java/dev/errfix/App.java:[10,33] cannot find symbol
  symbol:   method getMessage()
  location: class dev.errfix.App
[INFO] 1 error

エラー本文に lombok の語は 1 度も現れません。javac が報告しているのは「そのメソッドは無い」ということだけです。 件数は注釈を付けたフィールドの数ではなく、生成されるはずのメソッドを呼んでいる箇所の数になります (@Getter を 2 つ持つクラスを 3 行で 4 回呼ぶソースなら 4 errors)。数十件が一度に並ぶのはこのため。

同じ原因が、ツールによって次のように見えます。

  • javac 直接:error: cannot find symbol と、その下に symbol: method getMessage()
  • Maven:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.13.0:compile (default-compile) on project ...: Compilation failure
  • 注釈処理を戻したあとに出る 2 段目:Fatal error compiling: java.lang.ExceptionInInitializerError: com.sun.tools.javac.code.TypeTag :: UNKNOWN

直し方(Maven)

pom.xml の <properties> に <maven.compiler.proc>full</maven.compiler.proc> の 1 行を足します。 Lombok の依存宣言もソースコードも変えません(<maven.compiler.release> は元からある行です)。

<properties>
  <maven.compiler.release>21</maven.compiler.release>
  <maven.compiler.proc>full</maven.compiler.proc>  <!-- この 1 行を足す -->
</properties>

<dependencies>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.46</version>
    <scope>provided</scope>
  </dependency>
</dependencies>

この 1 行が読まれるのは maven-compiler-plugin 3.13.0 以降です。それより古いプラグインは full という値を知らず、 警告も出さずに無視します(3.12.1 で実測。ビルドログを proc で grep しても 1 行も引っかからないまま cannot find symbol が残る)。自分のビルドが使っているプラグイン版は、ログの --- compiler:3.13.0:compile (default-compile) --- の行に出ています。ここが 3.13.0 より小さければ、 プラグインの版も固定します。<build> ごと無ければ次をそのまま足し、maven-compiler-plugin の宣言が既にあるなら <version> の行だけを足します。

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.15.0</version>
    </plugin>
  </plugins>
</build>

プラグイン版を書いていないプロジェクトでは、Maven 本体が既定を決めます。Maven 3.9.12 がバインドするのは 3.13.0、 Maven 3.8.8 は 3.1(どちらも実測)。JDK を上げるついでに Maven も上げていれば前者、ビルドサーバの Maven が 据え置きなら後者になります。

直し方(プロセッサのパスを明示する)

maven.compiler.proc を使わず、Lombok を注釈プロセッサとして名指しする書き方でも通ります。 足すのは下の <build> だけで、<properties> と <dependencies> は元のままです (maven.compiler.proc は書きません)。

<properties>
  <maven.compiler.release>21</maven.compiler.release>
</properties>

<dependencies>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.46</version>
    <scope>provided</scope>
  </dependency>
</dependencies>

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.15.0</version>
      <configuration>
        <annotationProcessorPaths>
          <path>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.46</version>
          </path>
        </annotationProcessorPaths>
      </configuration>
    </plugin>
  </plugins>
</build>

<annotationProcessorPaths> はプロセッサを探すパスを決めるだけで、コンパイル時のクラスパスとは別物です。 <dependencies> のほうを消して <annotationProcessorPaths> だけにすると、今度は import lombok.Getter; の行が package lombok does not exist になります。両方に同じ座標を書くのが正しい形。

こちらは maven.compiler.proc を書かなくても注釈処理が走ります。理由は次の節にあります。 プロセッサを複数使うプロジェクト(Lombok と MapStruct の併用など)では、何を走らせるかがこの 1 箇所に集まります。 Lombok だけなら、前者の 1 行のほうが差分は小さい。

なぜ JDK 23 で出るのか

JDK 22 までの javac は、クラスパスを探して見つけた注釈プロセッサを、何の指定も無いまま実行していました。 Lombok を依存に足すだけで @Getter が展開されていたのは、この既定動作に乗っていたからです。

JDK 23 でその既定が変わり、注釈処理は明示的に要求されたときだけ走るようになりました。要求の仕方は javac の オプションで、次のどれか 1 つがあれば足ります。

  • -processor … プロセッサをクラス名で指定する
  • --processor-path / --processor-module-path … プロセッサを探すパスを指定する
  • -proc:only / -proc:full … 注釈処理を明示的に有効にする

Maven の maven.compiler.proc は -proc:full に、<annotationProcessorPaths> は --processor-path に対応します。 どれも渡していないビルドでは、プロセッサはクラスパスに置かれたまま起動しません。Lombok が動いていないので getter は生成されず、それを呼んでいる行が cannot find symbol になる——エラーが指しているのは呼び出し側で、 実際に止まっているのは 1 段手前の注釈処理です。

これは API の削除でも非推奨でもなく、既定値の変更です。JDK 21 と 22 は同じソースをコンパイルして成功させながら、 変更を予告する注意書きを出していました。

Note: Annotation processing is enabled because one or more processors were found
  on the class path. A future release of javac may disable annotation processing
  unless at least one processor is specified by name (-processor), or a search
  path is specified (--processor-path, --processor-module-path), or annotation
  processing is enabled explicitly (-proc:only, -proc:full).
  Use -Xlint:-options to suppress this message.
  Use -proc:none to disable annotation processing.

境界は JDK 22 と 23 のあいだにあります。開発機を 21 に据え置いたままビルドサーバだけ 23 以降へ上げた構成では、 落ちるのは片側だけになります。

Lombok の版はどこに効くか

依存の版を上げるのは、この症状に対しては空振りになります。Maven Central の現行である 1.18.46 に上げても、 注釈処理を有効にしない限り cannot find symbol は 1 件も減りません(JDK 25 で実測)。プロセッサが起動していない 状態は、プロセッサの中身と関係が無いからです。

版が問われるのは、注釈処理を戻したあとです。-proc:full が渡ると javac は Lombok を起動し、そこで初めて Lombok 側が JDK に追いついているかどうかが表に出ます。追いついていない組み合わせでは、Maven のログが 1 行だけこう変わります。

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.13.0:compile (default-compile) on project demo: Fatal error compiling: java.lang.ExceptionInInitializerError: com.sun.tools.javac.code.TypeTag :: UNKNOWN

javac を直接叩くと、同じ失敗がプロセッサ側の例外として出ます。

An annotation processor threw an uncaught exception.
Consult the following stack trace for details.
java.lang.ExceptionInInitializerError
	at lombok.javac.apt.LombokProcessor.placePostCompileAndDontMakeForceRoundDummiesHook(LombokProcessor.java:174)
	at lombok.javac.apt.LombokProcessor.init(LombokProcessor.java:96)
	at lombok.core.AnnotationProcessor$JavacDescriptor.want(AnnotationProcessor.java:160)

-proc:full を付けて同じソースをコンパイルしたときの終了コードです。

LombokJDK 23JDK 24JDK 25
1.18.30033
1.18.32033
1.18.34033
1.18.36033
1.18.38000
1.18.46000

注釈処理が止まるのは JDK 23 から、Lombok 自身が落ちるのは 24 から——層ごとに境界の版が違います。 JDK 23 へ上げただけなら、maven.compiler.proc を足せばここで測った範囲のどの Lombok でも通ります。24 以降へ 上げるなら 1.18.38 が下限で、Maven Central の版一覧に 1.18.36 と 1.18.38 の中間の版はありません。表に無い版は 測っていないので、ここから外挿はできません。

この上げには、コンパイルとは別の副作用が付いてきます。 1.18.38 へ上げる経路は必ず 1.18.34 を通過し、 その版から Lombok は生成メンバへ lombok.@Generated を既定で付けます。JaCoCo はその注釈が付いたメンバを 集計から外すので、コンパイルが通ったあと、カバレッジの母数だけが黙って縮みます(率は上がるため、閾値の チェックも止まりません)。カバレッジを測っているプロジェクトなら、 Lombok を上げると JaCoCo のカバレッジ母数が縮む を続けて読んでください。

JDK 24 以降では、注釈処理が成功したビルドにも sun.misc.Unsafe の警告が付いてきます。javac を直接叩いた場合は Lombok が名指しされ、終了コードは 0 のまま。

WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by lombok.permit.Permit
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release

Maven 経由のビルドにも同じ 3 行が出ますが、そちらが名指しするのは Maven 自身が同梱している guava です (com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper (file:/usr/share/maven/lib/guava.jar))。 どちらも BUILD SUCCESS に付随する警告で、cannot find symbol とも ExceptionInInitializerError とも別のもの。

切り分け

  • Gradle でビルドしている:同じ cannot find symbol が出ますが、境界は JDK の版ではありません。Gradle は 注釈プロセッサを annotationProcessor 構成から取るため、Lombok を compileOnly にしか宣言していないビルドは JDK 21 でも同じエラーになります(Gradle 9.6.1 で JDK 21 と 25 の両方を実測)。同じ座標を annotationProcessor にも宣言すると、-proc 相当の設定なしで通りました。
  • Lombok 以外のコード生成も同時に消えている(MapStruct の実装クラス、Dagger のコンポーネントなど): JDK 23 が変えたのは「クラスパスの探索だけに頼っていた注釈処理」全体の扱いで、Lombok 固有の変更ではありません。 上の 3 種類のオプションはプロセッサの種類を問わないので、直し方も共通です(本記事が実測したのは Lombok だけ)。
  • cannot find symbol の直前に package ... does not exist が出ている:生成メソッドではなく型そのものが 見つかっていない状態で、原因は別です。Jackson 2 → 3 の名前空間移行なら package com.fasterxml.jackson.databind does not exist の直し方、 javax.* → jakarta.* なら package javax.persistence does not exist の直し方 が同じ形のエラーを扱っています。
  • コンパイルは直ったが、今度はカバレッジの数字が変わった:層 2 で Lombok を上げた場合の副作用です。 ビルドは成功したまま JaCoCo の母数だけが減ります(率は上がる)。 Lombok を上げると JaCoCo のカバレッジ母数が縮む を参照してください。
  • IDE のビルドでは通る:IDE は自前のコンパイラ設定と Lombok 連携を持つことがあり、mvn や javac とは 別の経路で動きます。本記事が扱っているのはコマンドラインのビルド側で、IDE 側は検証していません。 CI だけが赤い場合は、まず CI のログで JDK 版とプラグイン版の 2 行を確認します。

検証環境

  • image: eclipse-temurin(digest 固定 sha256:68868d04... = Temurin 25.0.3+9)。Maven 3.9.12、 maven-compiler-plugin 3.13.0(Maven が既定でバインドした版)
  • 再現:Lombok 1.18.38 を provided で宣言した pom.xml(maven.compiler.proc なし)で mvn clean compile → cannot find symbol・終了コード 1
  • 修正:ソースも Lombok の版も変えず、<maven.compiler.proc>full</maven.compiler.proc> の 1 行だけを足して 同じコマンド → 終了コード 0
  • reproduce と fix の差は pom.xml の 1 行だけ。JDK・Lombok・App.java は同一
  • JDK 21 / 22 / 23 / 24 / 25 の比較は、各版の eclipse-temurin イメージで同じ App.java を javac に かけて採取した。JDK 21 と 22 の注意書きも、その出力そのまま
  • Lombok の版マトリクスは、同じ javac -proc:full を Lombok 1.18.30 / 1.18.32 / 1.18.34 / 1.18.36 / 1.18.38 / 1.18.46 に対して回した終了コード
  • maven-compiler-plugin は 3.11.0 / 3.12.1 / 3.13.0 / 3.14.1 / 3.15.0 を pom.xml で固定して比較。 Maven 本体の既定バインドは 3.9.12 と 3.8.8 で確認
  • --processor-path 単独、および -processor によるクラス名指定でも、-proc を渡さずに JDK 25 で コンパイルが通ることを確認している(<annotationProcessorPaths> が対応する経路)
  • <annotationProcessorPaths> だけを書いて <dependencies> から Lombok を外した形も試し、 package lombok does not exist になることを確認した
  • Gradle の 2 点は gradle:9-jdk21 と gradle:9-jdk25(Gradle 9.6.1)で採取
  • 本文がコピペ用に指しているのは Maven Central の現行(Lombok 1.18.46、maven-compiler-plugin 3.15.0)。 ハーネスが検証した 1.18.38 / 3.13.0 は、境界を示す点として本文に別に置いている
  • 引用したログは実際の出力そのままだが、絶対パス(検証用の作業ディレクトリ)とモジュール名だけは src/main/java/... と demo に短縮している

再現から修正までは errfix の検証ハーネスが機械的に確認しています。JDK 23 の既定変更と、注釈処理を要求する オプションの一覧は、JDK 23 のリリースノート「Annotation processing in javac disabled by default」 (JDK-8321314)で確認しました。JDK 21 と 22 が予告の注意書きを出すこと、-proc:full で旧挙動に戻せることも 同じ項に書かれています。OpenJDK の Quality Outreach による告知「JDK 23: Changes Default Annotation Processing Policy」は同じ内容の事前アナウンスです。maven.compiler.proc の値と既定は maven-compiler-plugin の compile ゴールのドキュメントによります。

検証(machine-verified)

この修正は eclipse-temurin@sha256:68868d04fa9cfd5f5c6abec0b5cef86d8de2bf9c62c37c7d3e4f0f80f5cfd7ff のバージョン固定コンテナ内で再現し、修正後に cannot find symbol のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs jvm/jdk23-annotation-processing-disabled-lombok
● reproduce cannot find symbol present ✓
● apply fix exit 0
● re-run cannot find symbol gone ✓
PASS verified · eclipse-temurin@sha256:68868d04fa9cfd5f5c6abec0b5cef86d8de2bf9c62c37c7d3e4f0f80f5cfd7ff · signature gone

確認したのは上のイメージの中だけです。別の環境で直らなかった、記述が違う、という場合は 報告してください(対象と検証イメージは件名・本文に入ります)。