本ページはプロモーションが含まれています

Java入門

Java Nullableとは?@Nullableとの使い分けとnull判定・NPE対策

CATEGORY
REV.
DRAWN BY
TOM
SHEETS
11枚
@Nullable(null判定とNPE対策)

トム

SESで約8年、環境を変えられないもどかしさの中にいました。32歳で自社開発企業に転職し、今はClaude Codeと自動化で、仕事も時間も組み直している途中です。
・Javaバックエンドエンジニア(Java歴12年以上)
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎
詳細なプロフィール

𝕏

Java Nullable とは、変数が null を持つことを許容する性質のことです。

Javaのオブジェクト型はデフォルトで Nullable なので、扱いを誤ると NullPointerException(NPE)の原因になります。

結論を先に言えば、NPEを防ぐ最短ルートは、次の3層を使い分けることです。
  • メソッドの戻り値 → Optional<T>
  • フィールド・引数 → @Nullable / @NonNull アノテーション
  • 外部入力など境界 → 従来の null チェック

筆者も NullPointerException に何度も悩まされてきました。その経験から言えば、この3層を意識するだけで、本番障害になるレベルのNPEは大半を防げます。

ただし「Nullableとnullの違いは?」「Optionalとの使い分けは?」で迷う人は多いはずです。実例コードで整理していきます。

この記事を読めば、以下のことができるようになります。

  • Javaにおける Nullable の基本概念を理解できる。
  • Optional 型の役割と Nullable との使い分けがわかる。
  • プリミティブ型や各種データ型で Nullable を安全に扱う方法を学べる。
  • アノテーションを活用してコードの安全性を高める方法を知ることができる。
  • NullPointerException に怯えることなく、自信を持ってJavaコードを書けるようになる。
  • NullPointerExceptionが起きる原因と、null判定の基本を押さえられる。

まずはnullとOptional・@Nullableアノテーションの使い分けを、実例コードで見ていきましょう。

NullPointerExceptionが起きる原因と、if文やObjectsクラスによるnull判定の基本も押さえます。

PARTS LIST /目次

JavaのNullableとは?

最初に、Javaにおける nullable の基本的な考え方を整理しましょう。

null許容型(Nullable)とは何か?

Nullable とは、変数が null を持つことを許容する性質のことです。

Javaでは、オブジェクト型の変数はデフォルトで Nullable です。

これは、変数が特定のオブジェクトを指している場合もあれば、何も指していない null の状態もあり得る、という意味です。

例えば、以下のような String 型の変数 name を考えます。

String name = "Yamada"; // "Yamada"という文字列オブジェクトを参照
name = null;         // 何も参照しない状態 (null)

String 型のようなオブジェクト型の変数は、初期化後に null を代入できます。これが nullable の基本的な性質です。

NullableとNotNullの違い

これに対し、null を持つことを許容しない性質を NotNullと呼びます。

Javaのプリミティブ型(int, boolean, double など)は、基本的に NotNullです。

プリミティブ型の変数は、必ず何らかの値を持ち、null を代入することはできません。

int age = 30; // age は 30 という値を持つ
// age = null; // コンパイルエラー!プリミティブ型にnullは代入できない

オブジェクト型がデフォルトで Nullable であるのに対し、プリミティブ型は NotNull となります。

Nullableを扱う理由と注意点

オブジェクト型が Nullable である理由は、「値が存在しない」状態を表現するためです。

例えば、データベース検索で該当するデータが見つからなかった場合や、オプションのパラメータが指定されなかった場合などに null を返せば、「存在しない」ことを示せます。

しかし、Nullable な変数を扱う際には注意が必要です。

null の可能性がある変数のメソッドを呼び出したり、フィールドにアクセスすると、NullPointerException が発生します。

String text = getSomeText(); // nullが返る可能性があるメソッド

// textがnullの場合、ここでNullPointerExceptionが発生する!
int length = text.length();

NullPointerException を避けるためには、Nullable な変数を扱う前に、必ず null チェックを行う必要があります。

String text = getSomeText();

if (text != null) {
    int length = text.length(); // nullでないことを確認してからアクセス
    System.out.println("Length: " + length);
} else {
    System.out.println("Text is null.");
}

Nullable は便利な反面、NullPointerException というリスクを伴います。このリスクをいかに管理するかが課題です。

nullを扱う3つの方法(何をして、どう効くか)
METHOD / 方法
何をするか
効き方
素の参照
null が入りうる
使う側が気づかないとNPE
Optional
戻り値で「無いかも」を型として表す
処理を強制的に意識させる
@Nullable / @NonNull
IDEや解析ツールが警告
実行時の動作は変えない

NullPointerExceptionの原因とnull判定の基本

Nullableを使い分ける前に、そもそもNullPointerException(ぬるぽ)がなぜ起き、どう防ぐのかを整理します。

NullPointerExceptionは、nullの変数を使おうとしたときに起きる実行時エラーです。コンパイル時には検出されず、実行して初めて発覚します。

NullPointerExceptionが起きる主な原因5つ

パターンは意外と限られています。次の5つを押さえれば、大半のNPEは事前に防げます。

  • 変数の初期化忘れ:参照型を宣言しただけで使うと起きます。メンバ変数はデフォルトでnullなので、コンストラクタやセッターでの設定漏れに注意します。
  • メソッドの戻り値がnull:データベース検索、APIコール、Map.get()などで、該当データがないとnullが返ります。
  • 配列・コレクションの要素がnull:ループ中に要素のメソッドを呼ぶと、その要素でエラーになります。
  • アンボクシング:IntegerやBooleanなどのラッパークラスがnullのまま、プリミティブ型に自動変換されると起きます。
  • メソッドチェーンの途中がnull:a.getB().getC().getName()のような呼び出しでは、どこが原因か特定しにくくなります。
String value = map.get("key");            // 存在しないキーはnull
int length = value.length();              // → NullPointerException

Integer count = null;
int total = count + 1;                    // アンボクシングでNullPointerException

String city = user.getAddress().getCity(); // getAddress()がnullならNullPointerException

エラーメッセージの読み方|Java 14以降は原因が具体的に出る

Java 14で、NullPointerExceptionのメッセージが大幅に改善されました(Java 15以降は標準で有効)。どの変数がnullで、どのメソッドを呼ぼうとしたかが具体的に示されます。ただし、ローカル変数名(下の例の"text")が表示されるのは-gオプション付きでコンパイルした場合で、付けないと"<local4>"のような番号表示になります。

Exception in thread "main" java.lang.NullPointerException:
  Cannot invoke "String.length()" because "text" is null

メッセージの変数名と、スタックトレースの行番号を照らし合わせれば、原因箇所をすばやく特定できます。古いバージョンでも、スタックトレースの行番号から辿り、その行でnullになりうる変数を1つずつ確認します。

if文と==・equalsによるnull判定

最も基本的なnull判定は、if文と==演算子の組み合わせです。nullでないときに処理するなら、!=を使います。

String name = getSomeName();            // nullを返す可能性がある
if (name == null) {
    System.out.println("名前が設定されていません。");
} else {
    System.out.println("ようこそ、" + name + "さん!");
}

よくある間違いが、==とequalsの混同です。nullかどうかの判定には==を使います。nullの変数に対してequalsを呼ぶと、NullPointerExceptionになります。

回避する定番のテクニックが、定数やリテラルを先に書くことです。リテラルはnullにならないため、変数がnullでもequalsはfalseを返すだけで済みます。

String text = getText();                 // nullの可能性がある
if ("hoge".equals(text)) {               // textがnullでも安全
    System.out.println("textは\"hoge\"です。");
}

Objectsクラスで意図を明確にする

java.util.Objectsには、nullを安全に扱うメソッドがそろっています。

  • Objects.isNull() / Objects.nonNull()(Java 8〜):obj == null・obj != nullと等価です。nullを判定している意図が、コード上で明確になります。
  • Objects.requireNonNull():引数がnullのとき、指定したメッセージ付きで即座にNullPointerExceptionをスローします。メソッドの先頭で引数を検証するパターンとして広く使われ、「このメソッドはnullを受け付けない」という設計意図も伝わります。
public void sendEmail(String to, String subject) {
    Objects.requireNonNull(to, "送信先アドレスはnullにできません");
    Objects.requireNonNull(subject, "件名はnullにできません");
    // ここに到達した時点で、to・subjectはnullではない
}

コレクション・配列のnullチェックとデフォルト値

ListやMap、配列を扱うときは、2種類のnullを意識します。コレクションや配列のインスタンス自体がnullの場合と、要素にnullが含まれる場合です。特に拡張for文は、コレクション自体がnullだと、ループに入る前にNullPointerExceptionが起きます。

List<String> names = getNames();          // リスト自体がnullかもしれない
if (names != null) {                      // 1. インスタンスのnullチェック
    for (String name : names) {
        if (name != null) {               // 2. 要素のnullチェック
            System.out.println(name.toUpperCase());
        }
    }
}
あわせて読む

nullをnullのまま後続の処理へ渡さず、早い段階で安全な値に置き換える方法もあります。たとえば三項演算子なら、String displayName = (inputName != null) ? inputName : "名無し";と1行で書けます。

処理が複雑になる場合は、素直にif文を使うほうが読みやすくなります。

設計の面では、そもそもnullを返さないのが効果的です。戻り値がないときはOptional.empty()、コレクションが空なら空リスト(Collections.emptyList())を返します。この方針をチームで統一すると、null判定のコード量を減らせます。

防御的プログラミング|外部入力は必ずnullを疑う

外部からの入力や、他のメソッドの戻り値のように、自分の管理外から渡される値は、常にnullの可能性があると仮定します。これが「防御的プログラミング」の考え方です。

特にハマりやすいのが、外部APIのレスポンスです。仕様書に「必ず値が返る」と書かれていても、実際にはnullが返ってくることがあります。仕様書を信用しすぎず、受け取ったデータにはnullチェックを入れるのが鉄則です。

Nullable・Optional・@Nullableの使い分け早見表

細かい解説に入る前に、まず「結局どれを使えばいいか」を1枚で確認できる早見表を示します。迷ったらここに戻ってきてください。

使う場所推奨非推奨理由
メソッドの戻り値Optional<T>素の null 返却シグネチャで「値がない可能性」を明示でき、呼び出し元の null チェック漏れを防げる
フィールド@Nullable / @NonNull アノテーションOptional 型のフィールドOptional は Serializable 非対応かつアンラップが冗長になる
メソッドの引数@Nullable / @NonNull + Objects.requireNonNullOptional 引数呼び出し元が空 Optional を渡すか null を渡すか曖昧になる
外部入力(DB/API/CLI)従来の if (x != null) チェックアノテーションのみ境界では型情報が失われるため、実行時チェックで防御する
プリミティブで null を表現したいラッパークラス(Integer 等)+ null チェックマジックナンバー(-1 等)「未設定」と「実値の-1」を混同しない
「未設定」を含む状態定数enum に UNKNOWN / NONE 値null + switch従来の switch は null でNPEを起こす(Java 21以降は case null で扱える)

「nullになり得る値をどこで扱っているか」から選ぶ流れを図にすると、次のようになります。

nullになり得る値を「どこで扱うか」で選ぶ
WHERE / どこで扱う?
選ぶもの
使い方
メソッドの戻り値
Optional<T> を返す
空なら Optional.empty()
フィールド・引数null を許す
@Nullable を付ける
使う前に null チェック
フィールド・引数null を許さない
@NonNullJSpecifyなら @NullMarked
引数は Objects.requireNonNull で検証
外部入力DB・API・CLI
if (x != null)
実行時にチェック
プリミティブ値
ラッパークラスInteger 等
アンボクシング前に null チェック
状態・区分
enum に UNKNOWN / NONE
null を入れない

以降の章でそれぞれの根拠と実例コードを順に解説します。

Optional型と Nullableの違いと使い分け

Java 8(2014年)でNPE対策として Optional 型が導入されました。

2026年現在主流のJava 17/21/25 LTS でも標準APIとして継続利用されており、本記事のコード例はそのまま動作します。

ここでは、Optional 型と Nullable の違い、そしてその使い分けについて解説します。

Optionalとは?

Optional は、null の可能性がある値をカプセル化するためのコンテナオブジェクトです。

Optional 自体は null になるべきではありません。

Optional を使えば「値が存在しないかもしれない」ことをシグネチャ上で明示できます。

Optional は以下の3つの状態を持ちます。

Optionalの状態

  1. 値が存在する状態: Optional.of(value) で生成。
  2. 値が存在しない状態(空の状態): Optional.empty() で生成。
  3. null かもしれない値から生成: Optional.ofNullable(value) で生成(value が null なら空の Optional、そうでなければ値を持つ Optional)。
// 値が存在する場合
Optional<String> opt1 = Optional.of("Hello");

// 値が存在しない場合 (空)
Optional<String> opt2 = Optional.empty();

// nullかもしれない値から生成
String nullableValue = getNullableValue(); // nullかもしれない値を取得
Optional<String> opt3 = Optional.ofNullable(nullableValue);

Optionalのメリット

Optional を使うメリットは以下のとおりです。

メリット

  1. NullPointerException の防止: Optional は値の取り出し方を明示的に書かせるため、NPEを起こしにくい(空の Optional に get() を呼ぶと、NPEではなく NoSuchElementException が発生する)
  2. コードの意図の明確化: メソッドの戻り値の型を見るだけで、「このメソッドは値がない場合がある」ことが明確に伝わる
  3. 流れるようなAPI: map(), flatMap(), filter() などのメソッドを使うことで、if 文による null チェックのネストを避け、読みやすいコードを書ける
Optional<String> optText = findTextById(123); // Optional<String> を返すメソッド

// 従来のnullチェック
// String text = findTextByIdLegacy(123); // String (nullable) を返すメソッド
// if (text != null) {
//    String upperText = text.toUpperCase();
//    if (upperText.length() > 5) {
//        System.out.println(upperText);
//    }
// }

// Optionalを使った場合
optText.map(String::toUpperCase)       // 値があれば大文字に変換
       .filter(s -> s.length() > 5) // 長さが5より大きいかフィルタリング
       .ifPresent(System.out::println); // 条件を満たせば出力

OptionalはNullableの代替になり得るか?

Optional は Nullable を完全に置き換えるものではありません。

Optional は主にメソッドの戻り値で「値が存在しないかもしれない」ことを示すために設計されています。

フィールドやメソッドの引数に Optional を使うことは、一般的には推奨されません。理由は以下のとおりです。

ポイント

  • 冗長性: フィールドが Optional 型だと、それを使うたびに Optional のラッパーを解除する必要があり、コードが冗長になる
  • シリアライズの問題: Optional は Serializable を実装していないため、シリアライズが必要なクラスのフィールドには使えない
  • 意図の不明確さ: 引数に Optional を使うと、「引数が必須なのか、省略可能なのか」が null を直接渡す場合よりも分かりにくくなる
Optional はメソッドの戻り値として Nullable の代替として有効ですが、フィールドや引数での使用は慎重に考えてください。

フィールドや引数には、従来の null チェックか後述するアノテーションを使い分けます。

よくある失敗例

たとえば、ユーザー検索APIがDBに該当レコードがない場合に null を返す設計だと、呼び出し側の null チェック漏れがそのまま本番の NullPointerException につながります。

戻り値を Optional<User> にすれば、シグネチャから「値がない可能性」が読み取れるようになり、チェック漏れを型で防げます。「Optionalは戻り値に使う」というルールが実運用で効くのはこのためです。

プリミティブ型のNullable対応

Javaのプリミティブ型(int, long, boolean など)は null を許容しません。

しかし、データベースのカラムが NULL 許容である場合など、「値が存在しない」状態をプリミティブ型で表現したいケースがあります。

intとIntegerの違い

プリミティブ型の int を Nullable として扱いたい場合は、ラッパークラスの Integer を使用します。

Integer はオブジェクト型であり、null を代入できます。

int primitiveInt = 10;
// primitiveInt = null; // コンパイルエラー

Integer wrapperInt = 10;
wrapperInt = null; // OK

int と Integer の関係と同様に、以下の通り他のプリミティブ型にも対応するラッパークラスが存在し、ラッパークラスを使って Nullable を表現します。

  • longとLong
  • boolean とBoolean
  • double と Double
  • float と Float
  • char と Character
  • byte と Byte
  • short と Short

ラッパークラスでNullableを実現する方法

ラッパークラスを使って Nullable なプリミティブ値を扱う際の基本的な方法は以下のとおりです。

  1. null の可能性がある場合はラッパークラスを使用する: データベースのカラムやAPIレスポンスなどで値が存在しない可能性がある場合は、対応するラッパークラス(Integer, Long, Boolean など)を変数の型として使用します。
  2. 使用前に null チェックを行う: ラッパークラス型の変数をプリミティブ型の変数に代入したり、プリミティブ型として扱われる計算に使用したりする前には、必ず null チェックを行います。
public void processUserData(Integer userId, Boolean isActive) {
    // userId (Integer) の null チェック
    if (userId != null) {
        // 安全に int として扱える
        int id = userId; // アンボクシング
        System.out.println("Processing user ID: " + id);
    } else {
        System.out.println("User ID is missing.");
    }

    // isActive (Boolean) の null チェック
    // Boolean.TRUE.equals() を使うと、nullの場合も安全に比較できる
    if (Boolean.TRUE.equals(isActive)) {
        System.out.println("User is active.");
    } else if (Boolean.FALSE.equals(isActive)) {
        System.out.println("User is inactive.");
    } else {
        // isActive が null の場合の処理
        System.out.println("User active status is unknown.");
    }
}

// 呼び出し例
processUserData(123, true);
processUserData(null, false);
processUserData(456, null);

ラッパークラスは Nullable なプリミティブ値を表現する標準的な方法です。ただしアンボクシング時のNPEは防げないため、使う前の null チェックは省けません。

あわせて読む

Nullableに使えるアノテーションの活用

コードレベルでの null チェックに加えて、アノテーションを使って Nullable / NotNullの情報をコードに付与し、静的解析ツールやIDEの支援を受ける方法があります。

Nullableアノテーションの種類と意味

Nullable や NotNull を示すためのアノテーションは、いくつかの仕様やライブラリで提供されています。代表的なものを紹介します。

  • JSR 305(事実上凍結。後継として JSpecify への移行が進行中):
    • @Nullable: 変数、パラメータ、メソッドの戻り値が null であることを許容する
    • @Nonnull: 変数、パラメータ、メソッドの戻り値が null であってはならないことを示す
    • @CheckForNull: @Nullable とほぼ同義だが、null チェックが推奨されることをより強調する場合がある
  • Spring Framework(org.springframework.lang。Spring Framework 7で非推奨):
    • @Nullable: JSR 305と同様。
    • @NonNull: JSR 305の @Nonnull と同様。
    • 2025年11月GAのSpring Framework 7(Spring Boot 4の土台)で非推奨になり、Spring自身もJSpecifyへ移行済み。
  • FindBugs / SpotBugs:
    • @Nullable / @CheckForNull
    • @NonNull
  • JSpecify(2024年7月に 1.0.0 リリース。Spring Framework 7 / Spring Boot 4 が標準採用):
    • org.jspecify.annotations.Nullable / NonNull / NullMarked / NullUnmarked の4つが1.0.0で確定。Google主導で、JetBrains・Oracle・Meta・Uber(NullAway)・Broadcom(Spring)などが合意形成して策定。Kotlinとの相互運用も考慮されています。
    • パッケージに @NullMarked を付けると「注釈なしは非null」が既定になり、nullを許す箇所だけ @Nullable を書けば済みます。
    • 新規プロジェクトでは JSpecify が第一候補。既存コードベースは JSR 305 / Spring の @Nullable から段階的に移行可能ですが、Spring Boot 4へ上げる際は Spring 側のアノテーションが非推奨になる点に注意。

@Nullable / @NonNull の使い分け

@Nullable と @NonNullの使い分けはシンプルです。

@Nullableを使う場面

次のようなケースで@Nullableを使います。

  • メソッドのパラメータが省略可能で、null が渡される可能性がある場合
  • メソッドが処理の結果として null を返す可能性がある場合
  • フィールドが初期化されず、null の状態があり得る場合
import org.jspecify.annotations.Nullable;

// JSpecify(org.jspecify:jspecify)を使用。JSR 305 の場合は javax.annotation.Nullable
public class UserProfile {
    private String name;

    @Nullable
    // 住所は任意入力かもしれない
    private String address;

    public UserProfile(String name, @Nullable String address) {
        this.name = name;
        this.address = address;
    }

    // @Nullable なパラメータ
    public void updateAddress(@Nullable String newAddress) {
        this.address = newAddress;
    }

    // @Nullable な戻り値
    @Nullable
    public String getAddress() {
        return this.address;
    }

    public String getName() {
        return this.name;
    }
}

@NonNull を使う場面

次のようなケースで@NonNull を使います。

  • メソッドのパラメータが必須であり、null が渡されてはいけない場合
  • メソッドが常に null 以外の値を返すことを保証する場合
  • フィールドがコンストラクタなどで必ず初期化され、null にならないことを保証する場合
import org.jspecify.annotations.NonNull; // JSpecify(org.jspecify:jspecify)を使用。JSR 305 の場合は javax.annotation.Nonnull
import org.jspecify.annotations.Nullable;

public class Order {

    @NonNull
    // 注文IDは必須
    private final String orderId;

    @Nullable
    // 備考は任意
    private String remarks;

    public Order(@NonNull String orderId) {
        // 引数も非nullを強制
        if (orderId == null) {
            // 本来は静的解析ツール(NullAway等)やIDEが検知するが、念のため明示チェック
            throw new IllegalArgumentException("Order ID cannot be null");
        }
        this.orderId = orderId;
    }

    @NonNull
    // 注文IDは必ず返る
    public String getOrderId() {
        return orderId;
    }

    public void setRemarks(@Nullable String remarks) {
        this.remarks = remarks;
    }

    @Nullable
    public String getRemarks() {
        return remarks;
    }
}

アノテーションによるIDE補助とコードの安全性

アノテーションの利点は、静的解析ツールやIDEがアノテーションを解釈し、潜在的な NullPointerException を警告してくれることです。

例えば、IntelliJ IDEAやEclipseなどのIDEは、@NonNull とマークされた変数に null を代入しようとしたり、@Nullable とマークされた変数を null チェックせずに使用しようとしたりすると、コーディング中に警告を表示します。

筆者のチームでは、レガシーJavaプロジェクトに @Nullable / @NonNull を後付けで導入したところ、IntelliJ IDEAの警告が初日で200件以上出てきました。

そのうち約3割は実際に null 起因のバグまたは潜在バグで、コードレビューでは見落としていた箇所でした。アノテーションは「動くコード」を「安全なコード」に変える静的チェッカとして、特にチーム規模が大きいほど効果が出ます。

Nullable活用例

Nullable の概念は、String、enumなど、さまざまな場面で登場します。それぞれのケースでの注意点と活用例を見ていきましょう。

Nullable Stringと空文字("")の違い|null判定の正しい順序

String 型変数を扱う際、null と空文字("")は別物で、判定の順序を誤るとNPEになります。

  • null: 変数がどのオブジェクトも参照していない状態。メモリ上に実体がない。
  • 空文字 (""): 長さ0の String オブジェクト。メモリ上に実体が存在する。
null と空文字は明確に区別して扱う必要があります。
String str1 = null;
String str2 = "";

// str1に対する操作は NullPointerException のリスクがある
// System.out.println(str1.length()); // NullPointerException!

// str2に対する操作は安全
System.out.println(str2.length()); // 0
System.out.println(str2.isEmpty()); // true

// null チェックと空文字チェック
if (str1 != null && !str1.isEmpty()) {
    System.out.println("str1 is not null and not empty.");
} else if (str1 == null) {
    System.out.println("str1 is null.");
} else { // str1 is empty
    System.out.println("str1 is empty.");
}
あわせて読む

enumの設計と初期値の考え方

enumは、定義された定数のいずれかを表すため、null になることは少ないです。

しかし、enum 型の変数をフィールドとして持つ場合など、初期状態や「未選択」の状態を表現するために null を使う場面も考えられます。

enum 型の変数もオブジェクト型であるため Nullable ですが、安易に null を使うのではなく、設計意図を明確にすることが重要です。
public enum Status {
    PENDING, PROCESSING, COMPLETED, FAILED;
}

public class Task {
    private String description;
    private Status currentStatus; // enum型の変数もNullable

    public Task(String description) {
        this.description = description;
        // 初期状態を null にするか、特定のenum値にするか?
        // this.currentStatus = null; // 「未開始」をnullで表現?
        this.currentStatus = Status.PENDING; // 「保留」を初期値とする?
    }

    public Status getCurrentStatus() {
        return currentStatus;
    }

    public void setCurrentStatus(Status currentStatus) {
        this.currentStatus = currentStatus;
    }
}

enum 変数が null の場合、switch 文で NullPointerException が発生する可能性があるため注意が必要です。

Task task = new Task("Some task");
task.setCurrentStatus(null); // nullを代入

// ...

// currentStatusがnullの場合、ここでNullPointerExceptionが発生する!
// switch (task.getCurrentStatus()) {
//    case PENDING:
//        System.out.println("Pending...");
//        break;
//    // ... 他のケース
// }

// nullチェックが必要
Status status = task.getCurrentStatus();
if (status != null) {
    switch (status) {
       case PENDING:
           System.out.println("Pending...");
           break;
       // ... 他のケース
       default:
            System.out.println("Unknown status");
            break;
    }
} else {
    System.out.println("Status is not set.");
}

「未設定」「該当なし」を表現したい場合、null は避けましょう。

代わりに UNKNOWN や NONE といった enum 定数を定義するのが安全です。

null チェックの手間を省き、コードの可読性を高められる場合があります。

よくある例: 受注ステータスの enum で「未設定」を null で表現していると、画面表示の switch 文が NullPointerException を起こします。

UNKNOWN 値を enum に追加し、既存データの null を UNKNOWN に置き換えれば、switch のNPEは起きなくなります。

「未設定」をnullで表現するのは "短期的にラク・長期的に高コスト" の典型例です。

Stream・ラムダ式でのNullable安全活用|filter(Objects::nonNull)パターン

Stream APIやラムダ式を使う際にも、Nullable な要素を安全に扱うための工夫が必要です。

StreamでのNullableの安全な使い方

Stream操作中に null 要素があると、map(String::toUpperCase) のように要素のメソッドを呼ぶ処理や、Collectors.toMap() への収集でNPEが発生します(Collectors.toList() 自体は null 要素もそのまま集めます)。

Streamパイプラインの途中で null を除去するか、null を安全に扱う操作を使いましょう。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.Objects;
import java.util.Optional;

public class StreamNullableDemo {

    public static void main(String[] args) {
        List<String> listWithNulls = Arrays.asList("apple", null, "banana", "cherry", null);

        // 不安全な例: mapでnull要素のメソッドを呼び出すとNPE
        // listWithNulls.stream()
        //         .map(String::toUpperCase) // nullでNPE
        //         .forEach(System.out::println);

        // 方法1: filterでnullを除去する
        System.out.println("--- Filter out nulls ---");
        listWithNulls.stream()
                .filter(Objects::nonNull) // nullでない要素のみを通過させる
                .map(String::toUpperCase)
                .forEach(System.out::println);

        // 方法2: map処理内でnullチェックを行う (少し冗長になる場合がある)
        System.out.println("--- Map with null check ---");
        listWithNulls.stream()
                .map(s -> (s == null) ? "[NULL]" : s.toUpperCase())
                .forEach(System.out::println);

        // 方法3: OptionalとflatMapを組み合わせる (より関数型らしい書き方)
        System.out.println("--- Optional and flatMap ---");
        listWithNulls.stream()
                .map(Optional::ofNullable) // 各要素をOptionalにラップ
                // .flatMap(opt -> opt.map(Stream::of).orElseGet(Stream::empty)) // Java 8
                .flatMap(Optional::stream) // Java 9以降の標準(Java 17/21/25 LTSでも利用可): OptionalをStreamに変換 (空なら空Stream)
                .map(String::toUpperCase)
                .forEach(System.out::println);

        // 収集 (Collectors) 時の注意:
        // デフォルトではnull要素があると toMap などで NPE になることがある
        List<String> safeList = listWithNulls.stream()
                                        .filter(Objects::nonNull)
                                        .collect(Collectors.toList());
        System.out.println("--- Collected non-null list ---");
        System.out.println(safeList);
    }
}

Objects.nonNull() や Optional を活用することで、Stream処理中に null を安全に扱うことができます。

filter(Objects::nonNull) は null 要素を除去する最も一般的な方法です。

filter(Objects::nonNull) の動き(方法1):null は map に届かない
ELEMENT / 要素
filter
map
"apple"
通過
"APPLE"
null
除去
呼ばれない
"banana"
通過
"BANANA"
"cherry"
通過
"CHERRY"
null
除去
呼ばれない
NOTE5要素のリストを stream() で上から順に流し、出力は APPLE / BANANA / CHERRY の3件。filter がないと、null に toUpperCase を呼んだ時点でNPEになります。

戻り値の設計|Optionalを使うべき場面とは

メソッドが値を返せない場合があるなら、その可能性を戻り値の型で呼び出し元に伝えるのが確実です。

メソッドの戻り値が null になり得るなら、Optional<T> の使用を強く推奨します。
  • 非推奨:null を直接返す。
    • 呼び出し元が null チェックを忘れると NullPointerException が発生する。メソッドのシグネチャだけでは null の可能性が伝わらない。
  • 推奨:Optional<T> を返す。
    • 型シグネチャで「値がない可能性」を明示できる。Optional が提供するメソッドにより、安全な値の取り扱いを強制または促進できる。
  • 代替案:
    • 空のコレクション: 結果がリストやマップの場合、null の代わりに空の Collections.emptyList() や Collections.emptyMap() を返す。これにより、呼び出し元は null チェックなしにループ処理などを行える。
    • Null Objectパターン: null の代わりに、何もしない(またはデフォルトの動作をする)特別なオブジェクトを返す。
import java.util.Optional;
import java.util.Map;
import java.util.HashMap;
import org.jspecify.annotations.Nullable; // JSpecify(org.jspecify:jspecify)

public class ReturnTypeDesign {

    private static Map<Integer, String> dataStore = new HashMap<>();
    static {
        dataStore.put(1, "Apple");
        dataStore.put(2, "Banana");
    }

    // 非推奨: nullを返す可能性
    @Nullable // アノテーションで示すことはできるが...
    public String findItemLegacy(int id) {
        return dataStore.get(id); // Map.getはキーがなければnullを返す
    }

    // 推奨: Optionalを返す
    public Optional<String> findItemOptional(int id) {
        return Optional.ofNullable(dataStore.get(id));
    }

    public static void main(String[] args) {
        ReturnTypeDesign design = new ReturnTypeDesign();

        // Legacy呼び出し (nullチェックが必要)
        String item1 = design.findItemLegacy(1);
        if (item1 != null) System.out.println(item1.toUpperCase());

        String item3Legacy = design.findItemLegacy(3);
        // System.out.println(item3Legacy.toUpperCase()); // NullPointerException!

        // Optional呼び出し (安全な処理)
        design.findItemOptional(2)
              .map(String::toUpperCase)
              .ifPresent(System.out::println); // 値があれば大文字で出力

        String item3 = design.findItemOptional(3)
                           .orElse("Not Found"); // 値がなければ"Not Found"
        System.out.println(item3);
    }
}

Optional を戻り値に使うことで、null 安全性を高め、APIの意図を明確に伝えられます。

引数の設計|@Nullable と @NonNull の選び方

メソッドの引数が null であることを許容するかどうか、そしてそれをどう扱うかも設計上のポイントです。

メソッドの引数が null であることを許容する場合は @Nullable アノテーションで明示し、メソッド内部で必ず null チェックを行うべきです。
  • パラメータが必須の場合:
    • @NonNull アノテーションを付与する。
    • メソッドの冒頭で Objects.requireNonNull(param, "param must not be null"); のようなチェックを行う。
  • パラメータが任意の場合:
    • @Nullable アノテーションを付与する。
    • メソッド内部で、そのパラメータを使用する前に null チェックを行う。
    • null の場合のデフォルト動作を定義する。
import java.util.Objects;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

public class ParameterDesign {

    // 必須パラメータ (NonNull) と任意パラメータ (Nullable)
    public void processData(@NonNull String id, @Nullable String tag, int value) {
        // 必須パラメータのnullチェック (アノテーションと合わせて二重チェックにもなる)
        Objects.requireNonNull(id, "ID cannot be null");

        System.out.println("Processing ID: " + id);
        System.out.println("Value: " + value);

        // 任意パラメータのnullチェック
        if (tag != null) {
            System.out.println("Tag: " + tag.toLowerCase());
        } else {
            System.out.println("Tag: [Not Provided]");
            // tagがnullの場合のデフォルト処理など
        }
    }

    // 引数にOptionalを使うのは一般的に非推奨
    // public void processOptionalData(Optional<String> optionalTag) { ... }

    public static void main(String[] args) {
        ParameterDesign design = new ParameterDesign();
        design.processData("user123", "important", 100);
        design.processData("item456", null, 200);
        // design.processData(null, "test", 300); // @NonNull違反 (IDEやツールが警告) / Objects.requireNonNullで実行時エラー
    }
}

パラメータの nullable 性を明確にし、メソッド内部で適切に処理することで、予期せぬ NullPointerException を防ぎ、メソッドの利用者に正しい使い方を伝えられます。

Optionalのアンチパターンと避けるべき書き方

Optional は正しく使えばNPE対策として強力ですが、使い方を誤ると「回りくどいnullチェック」に成り下がってしまいます。

ここでは実務でよく見かけるアンチパターンと、その改善方法をまとめます。

isPresent() + get() の多用を避ける

if (opt.isPresent()) { opt.get()... } という書き方は、if (x != null) { x... } という従来のnullチェックを単に置き換えただけで、Optional 本来の利点(メソッドチェーンによる宣言的な記述)を活かせていません。 map や orElseThrow などのメソッドを使い、値の取り出しをチェーンの中で完結させましょう。

Optional<String> opt = findName(userId);

// アンチパターン: isPresent() + get()
String result1;
if (opt.isPresent()) {
    result1 = opt.get().toUpperCase();
} else {
    result1 = "UNKNOWN";
}

// 改善: mapとorElseで宣言的に書く
String result2 = opt.map(String::toUpperCase).orElse("UNKNOWN");

orElseThrow()で値がない場合に例外を投げる

orElseThrow() は、値が存在しない場合に例外をスローするメソッドです。

Java 10以降は引数なしで呼び出すと NoSuchElementException をスローし、Java 8時点から使える orElseThrow(Supplier) ではカスタム例外を指定できます。

「IDで検索して、なければ例外」というSpring Bootのデータ取得における定番パターンです。

// Java 10+: 引数なし。値がなければ NoSuchElementException
User user1 = userRepository.findById(id).orElseThrow();

// カスタム例外を投げる(Spring Bootの定番パターン)
User user2 = userRepository.findById(id)
        .orElseThrow(() -> new UserNotFoundException("user not found: " + id));

注意点: Optional.of(x) で生成したOptional(x が非nullであることが保証されている)に対して orElseThrow() や orElse() を呼んでも、値が空になることはなく分岐は実質無意味です。

書いても害はありませんが、意図と合っていない可能性が高い書き方なので、Optional.of() なのか Optional.ofNullable() なのかを意識して使い分けましょう。

ifPresentOrElse()で値の有無を1行で分岐する

Java 9で追加された ifPresentOrElse(Consumer, Runnable) を使うと、値がある場合とない場合の両方の処理を1つの式で記述できます。

ifPresent() 単体では「値がある場合」しか扱えませんが、ifPresentOrElse() なら else 側の処理も同じ文脈で書けるため、if-else文の代替として読みやすくなります。

Optional<String> opt = findName(userId);

opt.ifPresentOrElse(
        name -> System.out.println("Hello, " + name),
        () -> System.out.println("Name is not registered.")
);

フィールド・エンティティにOptionalを使わない

前述の早見表で触れた通り、Optional は戻り値として使うことを意図した型であり、フィールドやJPAエンティティのプロパティに使うのは非推奨です。

Optional は Serializable を実装していないため、フィールドに使うとシリアライズが必要な場面(セッション格納・キャッシュ・分散処理など)で問題が起きます。

また、フィールドアクセスのたびに get()/orElse() でアンラップする必要があり、コードが冗長になります。

// アンチパターン: フィールドにOptionalを使う
public class User {
    private Optional<String> nickname; // NG: Serializable非対応・冗長
}

// 改善: フィールドは素の型 + @Nullable アノテーションで明示
public class User {
    @Nullable
    private String nickname; // OK: getter側でOptionalに変換して返す

    public Optional<String> getNickname() {
        return Optional.ofNullable(nickname);
    }
}

Java Nullableのよくある質問

Q1. Nullableとnullの違いは何ですか?

A1. Nullableは「nullを持つことを許容する性質」のことで、nullは実際にその変数が「何も参照していない状態」を指します。オブジェクト型はデフォルトでNullableという性質を持ちますが、実際にnullが入っているかどうかは別問題です。

Q2. @NullableとOptionalはどちらを使うべきですか?

A2. メソッドの戻り値にはOptional<T>、フィールドや引数には@Nullable/@NonNullアノテーションを使うのが基本です。Optionalはシリアライズ非対応かつアンラップが冗長になるため、フィールドでの使用は推奨されません。

Q3. プリミティブ型でnullを表現するにはどうすればいいですか?

A3. int・boolean等のプリミティブ型は直接nullを持てないため、対応するラッパークラス(Integer・Boolean等)を使います。使用前に必ずnullチェックを行うことが重要です。

Q4. JSR 305とJSpecify、どちらのアノテーションを使うべきですか?

A4. 新規プロジェクトではJSpecifyが第一候補です。JSR 305は事実上凍結しています。

JSpecifyはGoogle主導でJetBrains・Oracle・Broadcom(Spring)などが共同策定した後継仕様です。2024年7月に1.0.0が確定し、Spring Framework 7(Spring Boot 4)も標準採用しました。

既存コードベースは段階的に移行できます。ただしSpring 7ではSpring独自の @Nullable が非推奨です。

Q5. Stream処理でnull要素があるとどうなりますか?

A5. 要素のメソッドを呼ぶmap()の処理や、Collectors.toMap()への収集でNullPointerExceptionが発生します(Collectors.toList()はnull要素もそのまま集めます)。filter(Objects::nonNull)でnull要素を事前に除去するのが最も一般的な対処法です。

Q6. Javaでnull判定はどう書けばいいですか?

A6. 基本はif (x == null)とif (x != null)です。文字列の比較は、"リテラル".equals(変数)の順で書くと、変数がnullでもNullPointerExceptionを防げます。Java 8以降は、Objects.isNull()・Objects.nonNull()で意図を明確にできます。

Q7. NullPointerExceptionの主な原因は何ですか?

A7. 変数の初期化忘れ、メソッドの戻り値がnull、配列・コレクションの要素がnull、ラッパークラスのアンボクシング、メソッドチェーン途中のnullの5つです。

まとめ:Nullable設計で安全なJavaコードを実現

Javaのオブジェクト型はすべて Nullable です。扱い方を決めておくかどうかで、NullPointerException の発生頻度と保守のしやすさが大きく変わります。

最後に Nullable 設計のポイントを以下に整理します。

Optional・アノテーション

NullPointerException をなくすための特効薬は一つではありません。

Optional、null 安全性アノテーション (@Nullable, @NonNull)、そして従来の null チェックを、状況に応じて使い分けましょう。
  • メソッドの戻り値: null を返す代わりに Optional<T> を積極的に使用する。
  • フィールドとパラメータ: @Nullable / @NonNull アノテーションを活用し、変数が null を許容するかどうかを明確にする。
  • プリミティブ型: null を表現する必要がある場合は、対応するラッパークラス (Integer, Boolean など) を使用し、アンボクシング時の NullPointerException に注意する。
  • 従来の null チェック: アノテーションや Optional が使えないコードやライブラリとの連携部分では、if (variable != null) による基本的な null チェックが依然として重要となる。

nullを「許容する」より「明示する」

Nullable を扱う上での基本的な考え方として、null を暗黙的に「許容する」のではなく、「null の可能性がある」ことをコード上で「明示する」意識を持ちましょう。

Optional やアノテーションは、まさにこの「明示」のためのツールです。

null がどこで発生しうるのか、どこで null であってはならないのかがコードから読み取れるようになります。

  • null を返す可能性があるメソッドは Optional でラップする。
  • null を許容するパラメータには @Nullable を付ける。
  • null であってはならない変数には @NonNull を付ける。

null の存在を積極的に管理すれば、NullPointerException のリスクを大幅に低減できます。

結論: Optional を戻り値に、@Nullable/@NonNull をフィールド・引数に、null チェックは境界処理に。この3層で使い分ければNPEはほぼ防げます。

2026年時点の推奨スタック(新規プロジェクト向け): Java 17/21/25 LTS + Optional<T>(戻り値)+ JSpecify @NullMarked と @Nullable(フィールド・引数)+ Objects.requireNonNull(境界処理)。

既存コードがJSR 305 / Spring の @Nullable を使っている場合、今すぐ動かなくなるわけではありません。ただしSpring Framework 7(Spring Boot 4)では Spring の @Nullable が非推奨になるため、Spring Boot 4へ上げるタイミングでJSpecifyへ置き換えるのが無理のない移行手順です。

Javaの Nullable は確かに厄介な問題を引き起こします。しかし、その性質を正しく理解して Optional やアノテーションなどのモダンな機能を活用すれば、安全かつ効果的に扱えます。NPEは「正しく付き合えば防げる」ものです。

REVISIONS / 改訂履歴
REV.DATE内容
52026-10-15図解を描き直し(図面のデザインにそろえた)
42026-10-14雰囲気だけの挿絵を外した
32026-09-042026年9月時点の情報で全体を見直し
22026-05-022026年5月時点の情報で全体を見直し
12025-05-02初版
NEXT READ本棚から1本

AIで開発を組み直すなら、まずこの5冊から

入門・実践・設計まで、Claude Codeの本をレベル別に5冊選んで比べました。

→ おすすめ5冊を見る

  • この記事を書いた人
  • 最新記事

トム

SESで約8年、環境を変えられないもどかしさの中にいました。32歳で自社開発企業に転職し、今はClaude Codeと自動化で、仕事も時間も組み直している途中です。
・Javaバックエンドエンジニア(Java歴12年以上)
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎
詳細なプロフィール

𝕏

Java入門