---
title: Nullability Maven Plugin で Maven ユーザーが JSpecify/NullAway を使いやすくする
summary: この記事では、JSpecifyとNullAwayをMavenで導入し、Nullability Maven Pluginで設定を簡略化する方法を紹介します.
tags: ["NullAway", "JSpecify", "Maven", "Java"]
categories: ["Programming", "Java", "Maven", "NullAway"]
date: 2026-02-24T06:13:16.498Z
updated: 2026-05-08T01:49:42Z
---

### JSpecify とは？

[JSpecify](https://jspecify.dev/) とは、Java の nullability アノテーションを標準化するためのオープンソースプロジェクトです。

Java エコシステムでは従来、`@Nullable` や `@NonNull` といったアノテーションが JetBrains、FindBugs、Checker Framework など複数のライブラリでバラバラに定義されており、互換性の問題がありました。JSpecify はこれを統一し、ツール間で共通して使える仕様を提供することを目的としています。  
2006 年頃に提案された Java のソフトウェア欠陥検出用アノテーションの JCP 標準化仕様に JSR‑305（事実上廃止）がありましたが、JSpecify は型引数やジェネリクスに対する nullability の扱いなど、JSR‑305 では不十分だった領域をカバーしています。

`org.jspecify.annotations` パッケージ配下に `@Nullable`、`@NonNull`、`@NullMarked` などを定義しており、静的解析ツールや IDE がこれらを認識することで、より正確な null チェックが可能になります。

Spring Framework 7 は JSpecify に対応し、null を返すメソッドや null を渡してもよい引数には明示的に `@Nullable` をつけるようになりました。それ以外では null を返さず、null の設定も許容しません。  
詳しくは [こちらのブログ記事](https://spring.io/blog/2025/11/12/null-safe-applications-with-spring-boot-4) を参照してください。

JSpecify のアノテーションを使用することで、適切な null チェックが行われていないコードに対して、IDE は警告を表示できます。  
しかし、これ単体ではコンパイルエラーを発生させることはありません。

### NullAway とは？

NullAway は、Java の NullPointerException を静的解析で検出するツールです。[Error Prone](https://errorprone.info/) プラグインとして動作し、ビルド時に null 安全性の違反を警告・エラーとして報告します。  
JSpecify や JSR‑305 のアノテーションを認識し、「`@Nullable` が付いていない参照は non-null とみなす」というオプトアウト方式を採用しています。これにより既存コードベースへの段階的な導入がしやすい設計になっています。

NullAway と JSpecify を組み合わせることによって、適切な null チェックが行われていないコードに対して、コンパイルエラーを発生させることができるようになります。

例えば、次のコードを NullAway が有効な状態でコンパイルします。

```java
package com.example.foo;

import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;

@Component
public class Foo {

	private final RestClient restClient;

	public Foo(RestClient.Builder restClientBuilder) {
		this.restClient = restClientBuilder.build();
	}

	public String getFoo() {
		return this.restClient.get().uri("http://example.com/foo").retrieve().body(String.class);
	}

}
```

すると、次のコンパイルエラーが発生します。

```
[ERROR] /Users/toshiaki/.../Foo.java:[16,2] error: [NullAway] returning @Nullable expression from method with @NonNull return type
```

`RestClient` の `body` メソッドには次のように `@Nullable` アノテーションがついており、null を返す可能性があるのに、何もアノテーションやチェックを行わずに return しているからです。

```java
		/**
		 * Extract the body as an object of the given type.
		 * @param bodyType the type of return value
		 * @param <T> the body type
		 * @return the body, or {@code null} if no response body was available
		 * @throws RestClientResponseException by default when receiving a
		 * response with a status code of 4xx or 5xx. Use
		 * {@link #onStatus(Predicate, ErrorHandler)} to customize error response
		 * handling.
		 */
		<T> @Nullable T body(Class<T> bodyType);
```

次のように null の場合に対処するコードを書く、あるいはメソッドに `@Nullable` をつけて、null を返す可能性を伝播させることでコンパイルエラーを取り除くことができます。

```java
package com.example.foo;

import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;

import java.util.Objects;

@Component
public class Foo {

	private final RestClient restClient;

	public Foo(RestClient.Builder restClientBuilder) {
		this.restClient = restClientBuilder.build();
	}

	public String getFoo() {
		return Objects.requireNonNull(this.restClient.get().uri("http://example.com").retrieve().body(String.class),
				"Response body must not be null");
	}

}
```

なお、何もアノテーションを指定しない場合に non-null として扱うようにするには、クラスまたはパッケージに `@NullMarked` アノテーションをつける必要があります。  
上記の例では次のような `package-info.java` を用意してあります。

```java
@NullMarked
package com.example.foo;

import org.jspecify.annotations.NullMarked;
```

### JSpecify/NullAway を Maven で使う

ここまでで JSpecify/NullAway を使うことで、強制的に null チェックができるようになることがわかりました。さて、これを Maven に組み込むにはどうすればよいでしょうか。

次の `maven-compiler-plugin` に次のような設定が必要です（要: JDK 22+、または `-XDaddTypeAnnotationsToSymbol=true` フラグ付きの JDK 21.0.8+ / 17.0.19+（OpenJDK ベース））。

```xml
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.15.0</version>
        <configuration>
          <annotationProcessorPaths>
            <path>
              <groupId>com.google.errorprone</groupId>
              <artifactId>error_prone_core</artifactId>
              <version>2.49.0</version>
            </path>
            <path>
              <groupId>com.uber.nullaway</groupId>
              <artifactId>nullaway</artifactId>
              <version>0.13.4</version>
            </path>
          </annotationProcessorPaths>
          <fork>true</fork>
          <compilerArgs>
            <arg>-XDcompilePolicy=simple</arg>
            <arg>--should-stop=ifError=FLOW</arg>
            <!-- Required for JSpecify Mode on JDK 21.0.8+ / 17.0.19+ (OpenJDK). No-op on JDK 22+. -->
            <arg>-XDaddTypeAnnotationsToSymbol=true</arg>
            <!-- @formatter:off -->
            <arg>-Xplugin:ErrorProne -XepDisableAllChecks -XepOpt:NullAway:OnlyNullMarked=true -XepOpt:NullAway:JSpecifyMode=true -Xep:NullAway:ERROR -XepExcludedPaths:(.*/test/java/.*|.*/target/generated-sources/.*)</arg>
            <!-- @formatter:on -->
            <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
            <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
            <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
            <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
            <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
            <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
            <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
            <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
            <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
            <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
          </compilerArgs>
        </configuration>
      </plugin>
```

コピー＆ペーストすればよいのですが、Maven プロジェクトの量が多いと管理が大変になります…

### Nullability Maven Plugin の導入

Nullability Maven Plugin はこの `pom.xml` のボイラープレートを取り除いてくれる Maven プラグインです。

https://github.com/making/nullability-maven-plugin

前述の設定は Nullability Maven Plugin を使うと、次のように簡略化されます。

```xml
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.15.0</version>
</plugin>
<plugin>
    <groupId>am.ik.maven</groupId>
    <artifactId>nullability-maven-plugin</artifactId>
    <version>0.4.0</version>
    <extensions>true</extensions>
    <executions>
        <execution>
            <goals>
                <goal>configure</goal>
            </goals>
        </execution>
    </executions>
</plugin>
```

`maven-compiler-plugin` に自身で設定した内容があれば自動でマージされます。

> [!TIP]  
> `mvn help:effective-pom` でマージされた内容を確認できます。

このプラグイン自体の制約ではありませんが、NullAway の JSpecify Mode を使用するには JDK 22 以上、または OpenJDK ベース（Liberica / Temurin / Zulu など）の JDK 21.0.8+ もしくは 17.0.19+ でコンパイルする必要があります（ターゲットは 17 でも構いません）。後者の場合は `-XDaddTypeAnnotationsToSymbol=true` という javac フラグが必要ですが、Nullability Maven Plugin が JSpecify Mode 有効時に自動で付与するため、ユーザーが意識する必要はありません。Oracle JDK のようにこのフラグをサポートしない JDK で動かしたい場合や、その他のバージョン要件の詳細は [こちら](https://github.com/making/nullability-maven-plugin?tab=readme-ov-file#requirements) を参照してください。

### package-info.java の自動生成

JSpecify を使う上で面倒なのが、`package-info.java` の作成です。`@NullMarked` のついた `package-info.java` は各サブパッケージに作成する必要があり、つい作成を忘れがちです。

Nullability Maven Plugin はデフォルトで、`@NullMarked` がついていないクラス／パッケージがあればエラーにします。  
また、次の設定で `package-info.java` をビルド時に自動生成させることもできます。

```xml
<plugin>
    <groupId>am.ik.maven</groupId>
    <artifactId>nullability-maven-plugin</artifactId>
    <version>0.4.0</version>
    <extensions>true</extensions>
    <executions>
        <execution>
            <goals>
                <goal>configure</goal>
                <goal>generate-package-info</goal><!-- 追加 -->
            </goals>
        </execution>
    </executions>
</plugin>
```

生成させるディレクトリの設定は [こちら](https://github.com/making/nullability-maven-plugin?tab=readme-ov-file#generating-package-infojava-automatically) を参照してください。

---

Nullability Maven Plugin で Maven ユーザーが簡単に JSpecify / NullAway を導入できる方法を紹介しました。ぜひ試してください。
