これは、なにをしたくて書いたもの?
Javaの静的解析ツールといえばSpotBugs、PMD、CheckStyleあたりが有名ですが、Error Proneというものも試してみようかなと
ということで。
Error Prone
Error Proneは、Googleが開発しているJavaの静的解析ツールです。
GitHub - google/error-prone: Catch common Java mistakes as compile-time errors
Error Proneがどういうものかは、インストールドキュメントを見ると書かれています。
Our goal is to make it simple to add Error Prone checks to your existing Java compilation.
実際、ドキュメントを見るとjavacコマンドのプラグインおよびPluggable Annotation Processing APIを使って動作するものに
なっています。
ところで、javacコマンドにプラグインの機構があることを知りませんでした…。
Error Proneを使うには、Java 17以降が必要になるようです。
Please note that Error Prone must be run on JDK 17 or newer.
検出可能なバグパターンはこちら。
実験用とされているもの以外は、デフォルトですべて有効なようですね。
また自分でルールを追加することもできるようです。
今回は単純にセットアップして、GitHubリポジトリーのREADME.mdに記載されている簡単なサンプルを動かすくらいに
しておきたいと思います。使うビルドツールは、Apache Mavenとします。
環境
今回の環境はこちら。
$ java --version openjdk 21.0.7 2025-04-15 OpenJDK Runtime Environment (build 21.0.7+6-Ubuntu-0ubuntu124.04) OpenJDK 64-Bit Server VM (build 21.0.7+6-Ubuntu-0ubuntu124.04, mixed mode, sharing) $ mvn --version Apache Maven 3.9.10 (5f519b97e944483d878815739f519b2eade0a91d) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 21.0.7, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "6.8.0-60-generic", arch: "amd64", family: "unix"
準備
最初のpom.xmlの設定はこれくらいにしています。
<properties> <maven.compiler.release>21</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties>
Error Proneを使ってみる
ドキュメントに従って、Error Proneをインストールしてみます。
Apache Mavenの場合は、Maven Compiler Pluginに設定してインストールします。
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.14.0</version> <configuration> <compilerArgs> <arg>-XDcompilePolicy=simple</arg> <arg>--should-stop=ifError=FLOW</arg> <arg>-Xplugin:ErrorProne</arg> </compilerArgs> <annotationProcessorPaths> <path> <groupId>com.google.errorprone</groupId> <artifactId>error_prone_core</artifactId> <version>2.38.0</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build>
このあたりですね。
<arg>-Xplugin:ErrorProne</arg> <annotationProcessorPaths> <path> <groupId>com.google.errorprone</groupId> <artifactId>error_prone_core</artifactId> <version>2.38.0</version> </path> </annotationProcessorPaths>
では、GitHubリポジトリーのREADME.mdに載っているサンプルを使って試してみます。
GitHub - google/error-prone: Catch common Java mistakes as compile-time errors
こちらですね。
src/main/java/org/littlewings/errorprone/ShortSet.java
package org.littlewings.errorprone; import java.util.HashSet; import java.util.Set; public class ShortSet { public static void main(String[] args) { Set<Short> s = new HashSet<>(); for (short i = 0; i < 100; i++) { s.add(i); s.remove(i - 1); } System.out.println(s.size()); } }
$ mvn compile
すると、コンパイルに失敗しました…。
[INFO] Compiling 1 source file with javac [debug release 21] to target/classes
コンパイラで例外が発生しました(21.0.7)。バグ・データベース(https://bugs.java.com)で重複がないかをご確認のうえ、Javaのバグ・レポート・ページ(https://bugreport.java.com)から、Javaコンパイラに対するバグの登録をお願いいたします。レポートには、該当のプログラム、次の診断内容、およびJavaコンパイラに渡されたパラメータをご入力ください。ご協力ありがとうござ います。
java.lang.IllegalAccessError: class com.google.errorprone.BaseErrorProneJavaCompiler (in unnamed module @0x3380ca3d) cannot access class com.sun.tools.javac.api.BasicJavacTask (in module jdk.compiler) because module jdk.compiler does not export com.sun.tools.javac.api to unnamed module @0x3380ca3d
at com.google.errorprone.BaseErrorProneJavaCompiler.addTaskListener(BaseErrorProneJavaCompiler.java:85)
at com.google.errorprone.ErrorProneJavacPlugin.init(ErrorProneJavacPlugin.java:34)
at jdk.compiler/com.sun.tools.javac.api.BasicJavacTask.initPlugin(BasicJavacTask.java:256)
at jdk.compiler/com.sun.tools.javac.api.BasicJavacTask.initPlugins(BasicJavacTask.java:230)
at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.prepareCompiler(JavacTaskImpl.java:204)
at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.lambda$doCall$0(JavacTaskImpl.java:101)
at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.invocationHelper(JavacTaskImpl.java:152)
at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.doCall(JavacTaskImpl.java:100)
at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:94)
at org.codehaus.plexus.compiler.javac.JavaxToolsCompiler.compileInProcess(JavaxToolsCompiler.java:126)
at org.codehaus.plexus.compiler.javac.JavacCompiler.performCompile(JavacCompiler.java:214)
at org.apache.maven.plugin.compiler.AbstractCompilerMojo.execute(AbstractCompilerMojo.java:1226)
at org.apache.maven.plugin.compiler.CompilerMojo.execute(CompilerMojo.java:225)
at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:126)
at org.apache.maven.lifecycle.internal.MojoExecutor.doExecute2(MojoExecutor.java:328)
at org.apache.maven.lifecycle.internal.MojoExecutor.doExecute(MojoExecutor.java:316)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:212)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:174)
at org.apache.maven.lifecycle.internal.MojoExecutor.access$000(MojoExecutor.java:75)
at org.apache.maven.lifecycle.internal.MojoExecutor$1.run(MojoExecutor.java:162)
at org.apache.maven.plugin.DefaultMojosExecutionStrategy.execute(DefaultMojosExecutionStrategy.java:39)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:159)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:105)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:73)
at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build(SingleThreadedBuilder.java:53)
at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:118)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:261)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:173)
at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:101)
at org.apache.maven.cli.MavenCli.execute(MavenCli.java:906)
at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:283)
at org.apache.maven.cli.MavenCli.main(MavenCli.java:206)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:255)
at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:201)
at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:361)
at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:314)
ドキュメントを見返すと、設定が足りないようです。
.mvn/jvm.config
--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
再度コンパイル。
$ mvn compile
今度は動作するようになりました。
[INFO] --- compiler:3.14.0:compile (default-compile) @ error-prone-example ---
[INFO] Recompiling the module because of changed source code.
[INFO] Compiling 1 source file with javac [debug release 21] to target/classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] /path/to//src/main/java/org/littlewings/errorprone/ShortSet.java:[11,21] [CollectionIncompatibleType] Argument 'i - 1' should not be passed to this method; its type int is not compatible with its collection's type argument Short
(see https://errorprone.info/bugpattern/CollectionIncompatibleType)
[INFO] 1 error
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.162 s
[INFO] Finished at: 2025-06-15T22:39:25+09:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.14.0:compile (default-compile) on project error-prone-example: Compilation failure
[ERROR] /path/to/src/main/java/org/littlewings/errorprone/ShortSet.java:[11,21] [CollectionIncompatibleType] Argument 'i - 1' should not be passed to this method; its type int is not compatible with its collection's type argument Short
[ERROR] (see https://errorprone.info/bugpattern/CollectionIncompatibleType)
[ERROR]
[ERROR] -> [Help 1]
ひとまず、使い方の雰囲気はわかった感じですね。
あとは使っていって慣れていく感じでしょう。
検出するバグパターンの有効化、無効化
実験的に位置づけられているバグパターンはデフォルトでは無効になっているようなので、有効にするにはどうすれば
いいのかなと見てみたら、-Xep:<checkName>[:severity]で指定するようですね。
severityを指定することで、バグパターンの重要度を変更できたりするようです。
また、一括で設定するオプションもあるようですね。
- -XepAllErrorsAsWarnings
- -XepAllSuggestionsAsWarnings
- -XepAllDisabledChecksAsWarnings
- -XepDisableAllChecks
- -XepDisableAllWarnings
- -XepDisableWarningsInGeneratedCode
おわりに
Googleが開発しているJavaの静的解析ツール、Error Proneを試してみました。
この位置づけの静的解析ツールといえばSpotBugsかなと思っていたのですが、どうもカバー範囲が異なるみたいでどちらかを
選ぶ感じではなさそうですね。
なんとなく選ぶ印象でいたので、それぞれ使っていこうかなと思います。