Javaプログラミングにおいて、文字列を数値型に変換する操作は日常的に行われます。

しかし、その際に最も頻繁に遭遇するトラブルの一つがjava.lang.NumberFormatExceptionです。

この例外は、プログラムが想定していない形式の文字列を数値に変換しようとしたときにスローされます。

一見単純なエラーに見えますが、ユーザー入力のバリデーション不足や、外部システムから取得したデータの形式不整合など、発生の背景は多岐にわたります。

本記事では、この例外が発生する具体的な原因から、実務で使える高度な回避策、さらには最新のJava標準に合わせた修正方法までをプロの視点で徹底的に解説します。

java.lang.NumberFormatExceptionとは

java.lang.NumberFormatExceptionは、Javaの標準ライブラリに含まれる例外クラスの一つです。

この例外は、文字列を数値(Integer, Double, Floatなど)に変換しようとした際に、その文字列が適切な形式ではない場合にスローされます。

クラス階層としては、java.lang.RuntimeExceptionを継承したjava.lang.IllegalArgumentExceptionのサブクラスに位置付けられています。

つまり、非検査例外(Unchecked Exception)であるため、コンパイル時にはチェックされず、実行時に初めて問題が表面化するのが特徴です。

Javaで数値を扱う際、以下のようなメソッドを使用するとこの例外が発生する可能性があります。

  • Integer.parseInt(String)
  • Double.parseDouble(String)
  • Long.valueOf(String)
  • new BigDecimal(String)

これらのメソッドは、引数として渡された文字列を解析し、それぞれの型に応じた数値表現を構築しようとしますが、解析に失敗した瞬間に「これは数値として解釈できない」というシグナルとしてNumberFormatExceptionを投げます。

主な発生原因と具体例

この例外が発生する理由は、単に「数字ではない文字が入っている」というケースだけではありません。

開発者が意図しない微妙な文字の混入が原因となることが多々あります。

ここでは、代表的な5つの発生パターンを解説します。

非数値文字が含まれている場合

最も基本的で頻度の高い原因は、文字列の中にアルファベットや記号などの数値以外の文字が含まれているケースです。

Java
public class Main {
    public static void main(String[] args) {
        // アルファベットが含まれているため失敗する
        String input = "123a";
        int value = Integer.parseInt(input);
        System.out.println(value);
    }
}

上記のコードを実行すると、以下の結果が得られます。

Exception in thread "main" java.lang.NumberFormatException: For input string: "123a"
    at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
    at java.base/java.lang.Integer.parseInt(Integer.java:665)
    at java.base/java.lang.Integer.parseInt(Integer.java:781)
    at Main.main(Main.java:5)

エラーメッセージにFor input string: "123a"と表示されている通り、どの文字列が原因でエラーになったかが明確に示されます。

空文字や空白文字が含まれている場合

意外に見落としがちなのが、空文字(””)や、スペースのみの文字列を変換しようとするケースです。

Javaの数値変換メソッドは、空の入力を「0」として扱うことはありません。

Java
public class SpaceExample {
    public static void main(String[] args) {
        String emptyString = "";
        String spaceString = " 123 "; // 前後に半角スペースがある
        
        // 空文字の変換
        // int val1 = Integer.parseInt(emptyString); // ここで例外発生
        
        // 空白を含む文字列の変換
        int val2 = Integer.parseInt(spaceString); // ここでも例外発生
    }
}

特に、WebフォームやCSVファイルからデータを読み込む際、トリミング(前後空白削除)を行わずに処理すると、目に見えないスペースが原因でシステムがクラッシュすることがあります。

データ型の許容範囲を超えている場合(オーバーフロー)

数値として正しい形式であっても、そのデータ型が保持できる最大値・最小値を超えている場合、この例外が発生します。

最大値最小値
byte127-128
int2,147,483,647-2,147,483,648
long9,223,372,036,854,775,807-9,223,372,036,854,775,808

例えば、Integer.parseInt("3000000000")を実行すると、int型の範囲を超えているためNumberFormatExceptionとなります。

大きな数値を扱う可能性がある場合は、Long.parseLongBigIntegerを検討する必要があります。

通貨記号やカンマが含まれている場合

人間にとって読みやすい「1,000」や「¥5,000」といった形式は、Javaの標準的なparseメソッドでは処理できません。

Java
String price = "1,500";
// カンマが含まれているため例外が発生する
int val = Integer.parseInt(price);

このようなフォーマット済みの文字列を扱うには、java.text.DecimalFormatなどの専用クラスを使用し、ロケールを考慮した解析を行う必要があります。

基数(進数)の不一致

Integer.parseInt(String s, int radix)のように、進数を指定して変換を行う場合、指定した進数で許可されていない文字が含まれると例外が発生します。

例えば、2進数(基数2)として「102」を変換しようとした場合、「2」は2進数には存在しないためエラーとなります。

例外を回避するための基本的な対策

NumberFormatExceptionを未然に防ぎ、アプリケーションの堅牢性を高めるためには、いくつかの定石となる対策があります。

try-catch文による例外ハンドリング

最も直接的な方法は、例外が発生する可能性のある箇所をtry-catchで囲むことです。

これにより、不正な入力があった場合でもプログラムを異常終了させず、適切なエラーメッセージを表示したり、デフォルト値を代入したりすることが可能になります。

Java
public class SafeParse {
    public static void main(String[] args) {
        String input = "abc";
        int result;
        
        try {
            result = Integer.parseInt(input);
        } catch (NumberFormatException e) {
            System.err.println("エラー:数値以外の入力が検出されました。");
            result = 0; // デフォルト値を設定
        }
        
        System.out.println("結果: " + result);
    }
}

正規表現を用いた事前チェック

変換を試みる前に、文字列が数値のみで構成されているかを正規表現でチェックする手法も有効です。

これにより、例外の生成コストを抑えることができます。

例外のスローは計算リソースを消費するため、大量のデータを処理する場合は事前チェックが推奨されます。

Java
public class RegexCheck {
    public static boolean isNumeric(String str) {
        if (str == null || str.isEmpty()) {
            return false;
        }
        // 整数(負数を含む)を判定する正規表現
        return str.matches("-?\\d+");
    }

    public static void main(String[] args) {
        String test = "12345";
        if (isNumeric(test)) {
            int val = Integer.parseInt(test);
            System.out.println("変換成功: " + val);
        } else {
            System.out.println("変換不可な文字列です。");
        }
    }
}

文字列のトリミング処理

ユーザー入力や外部ファイルからのデータには、意図しない空白が含まれることが多いため、trim()メソッドを適用するのが鉄則です。

Java
String input = "  456  ";
// trim()により前後の空白を除去してからパースする
int value = Integer.parseInt(input.trim());

なお、Java 11以降では、より厳密な空白判定を行うstrip()メソッドも利用可能です。

全角スペースなども考慮したい場合はstrip()の使用を検討してください。

実践的な解決策とベストプラクティス

プロジェクトの規模が大きくなると、毎回try-catchを書くのはコードの可読性を低下させます。

より洗練された解決策を紹介します。

Apache Commons Langの活用

Java開発で広く使われているライブラリ「Apache Commons Lang」には、数値判定のための便利なユーティリティメソッドが含まれています。

  • NumberUtils.isParsable(String): 数値としてパース可能かどうかを判定します。
  • NumberUtils.toInt(String, int): 変換に失敗した場合に指定したデフォルト値を返します。
Java
import org.apache.commons.lang3.math.NumberUtils;

public class LibraryExample {
    public static void main(String[] args) {
        String input = "invalid";
        // 変換できない場合はデフォルト値の 0 を返す(例外は発生しない)
        int val = NumberUtils.toInt(input, 0);
        System.out.println("値: " + val);
    }
}

このような外部ライブラリを活用することで、ボイラープレートコード(定型文)を削減し、本質的なロジックに集中できるようになります。

Optionalを利用したモダンな記述

Java 8以降、Optionalを利用して値の有無を表現するスタイルが一般的になりました。

変換処理をラップしたヘルパーメソッドを作成しておくと、ストリーム処理などと相性が良くなります。

Java
import java.util.Optional;

public class OptionalParser {
    public static Optional<Integer> tryParseInt(String text) {
        try {
            return Optional.of(Integer.parseInt(text));
        } catch (NumberFormatException e) {
            return Optional.empty();
        }
    }

    public static void main(String[] args) {
        String input = "100";
        int value = tryParseInt(input).orElse(-1);
        System.out.println("結果: " + value);
    }
}

このアプローチは、関数型プログラミングのパラダイムに則っており、「値が存在しない(変換失敗)」という状態を型として安全に扱えるのが利点です。

数値フォーマッタの利用

カンマ区切りや通貨単位を含む文字列を扱う場合は、java.text.NumberFormatを使用するのが正解です。

Java
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;

public class FormatExample {
    public static void main(String[] args) {
        String price = "1,234,567";
        NumberFormat format = NumberFormat.getInstance(Locale.JAPAN);
        try {
            Number number = format.parse(price);
            long val = number.longValue();
            System.out.println("変換後: " + val);
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}

NumberFormatは、スレッドセーフではない点に注意が必要ですが、ロケールに応じた柔軟な解析が可能です。

よくあるトラブルシューティング

開発現場で実際に遭遇する、少し特殊なケースについても触れておきます。

1. 全角数字の扱い

Javaの標準的なInteger.parseIntは、全角数字(「123」など)を数値として認識しません

全角数字が混入する可能性がある場合は、事前に半角に変換するバリデーターを噛ませるか、正規表現での置換が必要です。

2. 小数点を含む文字列を整数型へ変換

Integer.parseInt("10.0")は失敗します。

たとえ小数点以下がゼロであっても、整数型へのパースには失敗します。

この場合は一度Double.parseDouble()で受け取り、その後にキャストするか、BigDecimalを利用して精度を保ちつつ変換を行う必要があります。

3. 符号の扱い

数値の先頭に「+」記号がついている場合、Javaのバージョンによっては挙動が異なることがありましたが、現在のモダンなJava(Java 8以降)ではInteger.parseInt("+123")は正常に動作します。

ただし、非常に古いシステムを保守している場合は注意が必要です。

まとめ

java.lang.NumberFormatExceptionは、Javaプログラマーであれば必ず一度は直面する例外です。

しかし、その原因を深く理解し、適切な対策を講じることで、バグの少ない堅牢なシステムを構築することができます。

本記事で解説したポイントを振り返ります。

  • 原因の特定:非数値文字、空白、型の上限、フォーマットの不整合が主な要因。
  • 基本対策try-catchによる保護と、trim()による事前洗浄。
  • 高度な手法:Apache Commons LangやOptionalを活用した、可読性の高いコード設計。
  • 適切なクラス選択:カンマを含む場合はNumberFormat、大きな数値はBigDecimal

プログラムの外部から入ってくるデータは、常に「汚れている」という前提で設計することが重要です。

「入力を信じず、常に検証する」という姿勢を持つことで、予期せぬNumberFormatExceptionに悩まされることは劇的に減るはずです。

この記事が、皆さんのJava開発におけるデバッグ作業や品質向上の助けとなれば幸いです。