NumberFormatExceptionとは、Integer.parseInt などが「数値として読めない文字列」を受け取ったときに投げる例外です。 メッセージの For input string: "..." の引用符の中に、読めなかった文字列がそのまま入っているので、そこを見れば原因はほぼ特定できます。

この記事では、空文字・null・前後の空白・全角数字・カンマ区切り・小数・桁あふれ・符号・16進表記を実際に入力し、結果とエラーメッセージを1つの早見表にまとめました。掲載したコードはすべて Java 17.0.20.1・21.0.10・25.0.4.1 で動かし、出力をそのまま載せています。原因別の対処と、そのまま貼って使える Optional 版の変換メソッドも用意しました。

NumberFormatExceptionの原因と対処の早見表

Integer.parseInt(入力) に渡したときの結果です(Java 17・21・25 で同じ結果)。

入力 結果 表示されるメッセージ 対処
"42" 42 (正常) そのまま使える
""(空文字) 例外 For input string: "" 空かどうかを先に判定する
null 例外 Cannot parse null string null を先に判定する
" 42" "42 "(半角空白つき) 例外 For input string: " 42" strip() で前後の空白を取る
" 42"(全角空白つき) 例外 For input string: " 42" strip()(trim() では取れない)
"123"(全角数字) 123 (正常に読める) 半角にそろえたいときは NFKC で正規化
"1,234"(カンマ区切り) 例外 For input string: "1,234" カンマを除くか NumberFormat で読む
"3.14"(小数) 例外 For input string: "3.14" BigDecimal か Double.parseDouble
"1e3"(指数表記) 例外 For input string: "1e3" BigDecimal か Double.parseDouble
"2147483648"(int の上限+1) 例外 For input string: "2147483648" Long.parseLong か BigInteger
"+5" "-5" 5 / -5 (正常) 先頭の符号は1つまで読める
"--5" "-" 例外 For input string: "--5" 符号だけ・符号の重複を除く
"0x1F" 例外 For input string: "0x1F" Integer.decode("0x1F")
"1F" 例外 For input string: "1F" Integer.parseInt("1F", 16)
"12a" "42L" "1_000" 例外 For input string: "12a" など 数字以外を取り除くか入力を見直す

まず引用符の中身を見て、空・空白・カンマ・小数点・桁数のどれに当たるかを確かめるのが近道です。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Java NumberFormatExceptionの原因と対処|数値チェック」

NumberFormatExceptionはどんな例外か

NumberFormatException は java.lang パッケージの例外で、IllegalArgumentException の子クラスです(後述のコードで親クラスを表示して確かめました)。RuntimeException の系統なので、try-catch を書かなくてもコンパイルは通ります。そのぶん、入力が想定と違ったときに初めて気づくことが多い例外です。

Java 21 の公式 API ドキュメント(Integer クラス)を今回開いて確認したところ、parseInt(String s, int radix) が NumberFormatException を投げる条件は次の4つでした。

  1. 引数が null、または長さ 0 の文字列
  2. 基数(radix)が Character.MIN_RADIX より小さい、または Character.MAX_RADIX より大きい
  3. 指定した基数の数字でない文字が含まれる(先頭の - と + は、文字列の長さが2以上のときだけ許される)
  4. 文字列が表す値が int の範囲に収まらない

早見表の例は、すべてこの4つのどれかに当てはまります。

スタックトレースから原因の行を見つける

例外を捕まえずに終わると、次のように表示されます。

public class Uncaught {
    public static void main(String[] args) {
        String input = "12a";
        int n = toNumber(input);
        System.out.println(n);
    }

    static int toNumber(String s) {
        return Integer.parseInt(s);
    }
}

実行結果(Java 21.0.10、java Uncaught.java):

Exception in thread "main" java.lang.NumberFormatException: For input string: "12a"
    at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
    at java.base/java.lang.Integer.parseInt(Integer.java:662)
    at java.base/java.lang.Integer.parseInt(Integer.java:778)
    at Uncaught.toNumber(Uncaught.java:9)
    at Uncaught.main(Uncaught.java:4)

読む順番は次のとおりです。

見る場所 読み取れること
1行目の For input string: "12a" 読めなかった文字列は 12a
at java.base/... の行 JDK の内部。ここは読み飛ばしてよい
最初に出てくる自分のクラスの行 Uncaught.toNumber(Uncaught.java:9) 9行目の parseInt が発生箇所
その下の Uncaught.main(Uncaught.java:4) 4行目から呼ばれている。値の出どころをたどる

java.base の行番号(662・778)は JDK の版で変わります。Java 17.0.20.1 では 668・786、Java 25.0.4.1 では 565・662 でした。自分のコードの行番号は変わらないので、そこを起点に調べます。

入力パターンを全部再現するコード

早見表の元になったコードです。1ファイルで動くので、保存して java NfeTable.java で実行できます。

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.function.Function;

public class NfeTable {
    static void run(String label, String in, Function<String, Object> f) {
        String shown = in == null ? "null" : "\"" + in + "\"";
        try {
            System.out.println(label + "(" + shown + ") => " + f.apply(in));
        } catch (RuntimeException e) {
            System.out.println(label + "(" + shown + ") => " + e.getClass().getSimpleName() + ": " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        System.out.println("java.version = " + System.getProperty("java.version"));
        String[] inputs = {
            "42", "", null, " 42", "42 ", " " + "42", "123", "1,234", "3.14", "1e3",
            "2147483647", "2147483648", "-2147483649", "+5", "-5", "--5", "-", "0x1F", "1F",
            "12a", "1_000", "42L", "0x1"
        };
        for (String s : inputs) {
            run("parseInt", s, Integer::parseInt);
        }
        System.out.println("--- 16進・その他のメソッド ---");
        run("parseInt(s,16)", "1F", s -> Integer.parseInt(s, 16));
        run("parseInt(s,16)", "0x1F", s -> Integer.parseInt(s, 16));
        run("decode", "0x1F", Integer::decode);
        run("decode", "#FF", Integer::decode);
        run("decode", "010", Integer::decode);
        run("parseInt", "010", Integer::parseInt);
        run("valueOf", "abc", Integer::valueOf);
        run("parseLong", "2147483648", Long::parseLong);
        run("parseLong", "9223372036854775808", Long::parseLong);
        run("parseLong", null, Long::parseLong);
        run("parseDouble", "3.14", Double::parseDouble);
        run("parseDouble", " 3.14 ", Double::parseDouble);
        run("parseDouble", "1e3", Double::parseDouble);
        run("parseDouble", "", Double::parseDouble);
        run("parseDouble", "abc", Double::parseDouble);
        run("parseDouble", "1,234.5", Double::parseDouble);
        run("parseDouble", null, Double::parseDouble);
        run("parseDouble", "NaN", Double::parseDouble);
        run("new BigDecimal", "3.14", BigDecimal::new);
        run("new BigDecimal", "", BigDecimal::new);
        run("new BigDecimal", " 3.14", BigDecimal::new);
        run("new BigDecimal", "1,234", BigDecimal::new);
        run("new BigDecimal", "abc", BigDecimal::new);
        run("new BigDecimal", null, BigDecimal::new);
        run("new BigInteger", "99999999999999999999", BigInteger::new);
        run("new BigInteger", "", BigInteger::new);
        run("new BigInteger", "12a", BigInteger::new);
    }
}

実行結果(Java 21.0.10。Java 17.0.20.1 は1行目のバージョン表示以外すべて同じ):

java.version = 21.0.10
parseInt("42") => 42
parseInt("") => NumberFormatException: For input string: ""
parseInt(null) => NumberFormatException: Cannot parse null string
parseInt(" 42") => NumberFormatException: For input string: " 42"
parseInt("42 ") => NumberFormatException: For input string: "42 "
parseInt(" 42") => NumberFormatException: For input string: " 42"
parseInt("123") => 123
parseInt("1,234") => NumberFormatException: For input string: "1,234"
parseInt("3.14") => NumberFormatException: For input string: "3.14"
parseInt("1e3") => NumberFormatException: For input string: "1e3"
parseInt("2147483647") => 2147483647
parseInt("2147483648") => NumberFormatException: For input string: "2147483648"
parseInt("-2147483649") => NumberFormatException: For input string: "-2147483649"
parseInt("+5") => 5
parseInt("-5") => -5
parseInt("--5") => NumberFormatException: For input string: "--5"
parseInt("-") => NumberFormatException: For input string: "-"
parseInt("0x1F") => NumberFormatException: For input string: "0x1F"
parseInt("1F") => NumberFormatException: For input string: "1F"
parseInt("12a") => NumberFormatException: For input string: "12a"
parseInt("1_000") => NumberFormatException: For input string: "1_000"
parseInt("42L") => NumberFormatException: For input string: "42L"
parseInt("0x1") => NumberFormatException: For input string: "0x1"
--- 16進・その他のメソッド ---
parseInt(s,16)("1F") => 31
parseInt(s,16)("0x1F") => NumberFormatException: For input string: "0x1F" under radix 16
decode("0x1F") => 31
decode("#FF") => 255
decode("010") => 8
parseInt("010") => 10
valueOf("abc") => NumberFormatException: For input string: "abc"
parseLong("2147483648") => 2147483648
parseLong("9223372036854775808") => NumberFormatException: For input string: "9223372036854775808"
parseLong(null) => NumberFormatException: Cannot parse null string
parseDouble("3.14") => 3.14
parseDouble(" 3.14 ") => 3.14
parseDouble("1e3") => 1000.0
parseDouble("") => NumberFormatException: empty String
parseDouble("abc") => NumberFormatException: For input string: "abc"
parseDouble("1,234.5") => NumberFormatException: For input string: "1,234.5"
parseDouble(null) => NullPointerException: Cannot invoke "String.trim()" because "in" is null
parseDouble("NaN") => NaN
new BigDecimal("3.14") => 3.14
new BigDecimal("") => NumberFormatException: null
new BigDecimal(" 3.14") => NumberFormatException: Character   is neither a decimal digit number, decimal point, nor "e" notation exponential mark.
new BigDecimal("1,234") => NumberFormatException: Character , is neither a decimal digit number, decimal point, nor "e" notation exponential mark.
new BigDecimal("abc") => NumberFormatException: Character a is neither a decimal digit number, decimal point, nor "e" notation exponential mark.
new BigDecimal(null) => NullPointerException: Cannot invoke "String.toCharArray()" because "val" is null
new BigInteger("99999999999999999999") => 99999999999999999999
new BigInteger("") => NumberFormatException: Zero length BigInteger
new BigInteger("12a") => NumberFormatException: For input string: "12a"

意外に見える結果を3つ挙げます。

  • 全角数字の "123" は 123 として読めます。 parseInt は各文字を Character.digit で判定するため、Unicode で数字と分類される文字は通ります。一方、全角の x や全角空白は通りません。
  • "010" は parseInt では 10、decode では 8 です。decode は先頭の 0 を8進数の印として扱います。
  • null を渡したときの例外はメソッドによって違います。 Integer.parseInt(null) と Long.parseLong(null) は NumberFormatException、Double.parseDouble(null) と new BigDecimal(null) は NullPointerException でした。

エラーメッセージの読み方

メッセージの形は、メソッドごとに少しずつ違います。上の実行結果から整理しました。

メッセージ 出るとき 意味
For input string: "abc" parseInt / parseLong / parseDouble / BigInteger 引用符の中が読めなかった文字列
For input string: "" parseInt / parseLong に空文字 引用符の中が空=空文字が渡された
Cannot parse null string parseInt / parseLong に null 変数が null のまま渡された
For input string: "0x1F" under radix 16 基数つきの parseInt 指定した基数では読めない文字がある
empty String Double.parseDouble("") 空文字
Zero length BigInteger new BigInteger("") 空文字
Character , is neither a decimal digit number, decimal point, nor "e" notation exponential mark. new BigDecimal("1,234") 読めなかった1文字が Character の直後に出る
メッセージが null new BigDecimal("") 空文字。メッセージ本文は付かない

引用符の中に空白や全角文字が混じっていないかを見るのがこつです。" 42" のように先頭に空白が1つあるだけでも、表示をよく見ると引用符の直後にすき間があります。ログに出すときは "[" + input + "]" のように角かっこで囲むと、空白や空文字が見分けやすくなります。

Java 8 で表示が違うところ

同じコードを Java 8(1.8.0_504)でも動かしたところ、結果(読める・読めない)は同じで、メッセージだけ次の点が違いました。

入力 Java 8 Java 17・21・25
Integer.parseInt(null) メッセージが null Cannot parse null string
Integer.parseInt("0x1F", 16) For input string: "0x1F" 末尾に under radix 16 が付く
new BigDecimal("1,234") メッセージが null Character , is neither ...
Double.parseDouble(null) の NullPointerException メッセージが null Cannot invoke "String.trim()" because "in" is null(25 では String.length())

Java 8 で java.lang.NumberFormatException: null と出たら、null を渡したか、BigDecimal に読めない文字列を渡したと読み替えられます。

原因別の対処

ここからは原因ごとに、直す前と直した後を並べます。確認に使ったコードは次の1ファイルです(java NumberChecks.java)。

import java.math.BigDecimal;
import java.text.Normalizer;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.util.Locale;

public class NumberChecks {
    public static void main(String[] args) throws Exception {
        System.out.println("--- 正規表現と isDigit ---");
        System.out.println("\"99999999999\".matches(\"[+-]?[0-9]+\") = " + "99999999999".matches("[+-]?[0-9]+"));
        System.out.println("\"123\".matches(\"\\\\d+\") = " + "123".matches("\\d+"));
        System.out.println("\"123\".matches(\"[0-9]+\") = " + "123".matches("[0-9]+"));
        System.out.println("\"123\".chars().allMatch(Character::isDigit) = " + "123".chars().allMatch(Character::isDigit));
        System.out.println("\"\".chars().allMatch(Character::isDigit) = " + "".chars().allMatch(Character::isDigit));

        System.out.println("--- trim と strip ---");
        System.out.println("trim : [" + " 42 ".trim() + "] [" + " 42".trim() + "]");
        System.out.println("strip: [" + " 42 ".strip() + "] [" + " 42".strip() + "]");
        System.out.println("NFKC: " + Normalizer.normalize("123,456.7 -5 +8", Normalizer.Form.NFKC));

        System.out.println("--- NumberFormat ---");
        NumberFormat nf = NumberFormat.getInstance(Locale.JAPAN);
        System.out.println("parse(\"1,234\") = " + nf.parse("1,234") + " (" + nf.parse("1,234").getClass().getSimpleName() + ")");
        System.out.println("parse(\"1,234.5\") = " + nf.parse("1,234.5") + " (" + nf.parse("1,234.5").getClass().getSimpleName() + ")");
        System.out.println("parse(\"12abc\") = " + nf.parse("12abc"));
        try {
            nf.parse("abc");
        } catch (java.text.ParseException e) {
            System.out.println("parse(\"abc\") => " + e.getClass().getSimpleName() + ": " + e.getMessage());
        }
        for (String s : new String[] { "1,234", "12abc" }) {
            ParsePosition pos = new ParsePosition(0);
            Number num = nf.parse(s, pos);
            boolean all = pos.getIndex() == s.length();
            System.out.println("厳密チェック \"" + s + "\" -> 値=" + num + " 読めた位置=" + pos.getIndex() + " 全部読めた=" + all);
        }
        NumberFormat de = NumberFormat.getInstance(Locale.GERMANY);
        System.out.println("GERMANY parse(\"1.234,5\") = " + de.parse("1.234,5"));
        System.out.println("JAPAN   parse(\"1.234,5\") = " + nf.parse("1.234,5"));

        System.out.println("--- 桁あふれの受け方 ---");
        System.out.println("Long.parseLong(\"2147483648\") = " + Long.parseLong("2147483648"));
        try {
            System.out.println(Math.toIntExact(Long.parseLong("2147483648")));
        } catch (ArithmeticException e) {
            System.out.println("Math.toIntExact => " + e.getClass().getSimpleName() + ": " + e.getMessage());
        }
        System.out.println("new BigDecimal(\"3.14\").intValue() = " + new BigDecimal("3.14").intValue());
        try {
            System.out.println(new BigDecimal("3.14").intValueExact());
        } catch (ArithmeticException e) {
            System.out.println("intValueExact => " + e.getClass().getSimpleName() + ": " + e.getMessage());
        }
        System.out.println("(int) Double.parseDouble(\"3.99\") = " + (int) Double.parseDouble("3.99"));
        System.out.println("Integer.parseUnsignedInt(\"4294967295\") = " + Integer.parseUnsignedInt("4294967295"));
        System.out.println("Integer.parseInt(\"-ff\", 16) = " + Integer.parseInt("-ff", 16));
        System.out.println("Integer.parseInt(\"1_000\".replace(\"_\", \"\")) = " + Integer.parseInt("1_000".replace("_", "")));
    }
}

実行結果(Java 21.0.10。Java 17.0.20.1・25.0.4.1 も同じ出力):

--- 正規表現と isDigit ---
"99999999999".matches("[+-]?[0-9]+") = true
"123".matches("\\d+") = false
"123".matches("[0-9]+") = false
"123".chars().allMatch(Character::isDigit) = true
"".chars().allMatch(Character::isDigit) = true
--- trim と strip ---
trim : [42] [ 42]
strip: [42] [42]
NFKC: 123,456.7 -5 +8
--- NumberFormat ---
parse("1,234") = 1234 (Long)
parse("1,234.5") = 1234.5 (Double)
parse("12abc") = 12
parse("abc") => ParseException: Unparseable number: "abc"
厳密チェック "1,234" -> 値=1234 読めた位置=5 全部読めた=true
厳密チェック "12abc" -> 値=12 読めた位置=2 全部読めた=false
GERMANY parse("1.234,5") = 1234.5
JAPAN   parse("1.234,5") = 1.234
--- 桁あふれの受け方 ---
Long.parseLong("2147483648") = 2147483648
Math.toIntExact => ArithmeticException: integer overflow
new BigDecimal("3.14").intValue() = 3
intValueExact => ArithmeticException: Rounding necessary
(int) Double.parseDouble("3.99") = 3
Integer.parseUnsignedInt("4294967295") = -1
Integer.parseInt("-ff", 16) = -255
Integer.parseInt("1_000".replace("_", "")) = 1000

空文字とnullは変換の前に判定する

入力欄が未入力のまま送られた、設定ファイルの値が空だった、Map から取り出した値が無かった、というときに起きます。変換の前に s == null || s.isBlank() で分けておくと、「未入力」と「数値でない」を別の扱いにできます。

// 直す前:未入力のとき例外で止まる
int count = Integer.parseInt(input);

// 直した後:未入力は既定値、それ以外は変換
int count = (input == null || input.isBlank()) ? 0 : Integer.parseInt(input.strip());

isBlank() は Java 11 以降で使えます。空白だけの文字列も true になるので、isEmpty() より入力チェックに向いています。

前後の空白はstripで取る

" 42" や "42 " は parseInt では読めません。CSV の列やコピーした値に空白や改行が付いていると起きます。

メソッド 半角空白 " 42 " 全角空白 " 42"
trim() 42 42(残る)
strip()(Java 11 以降) 42 42

実行結果のとおり、全角空白まで取れるのは strip() です。日本語の入力を扱うなら strip() を選ぶと安心です。なお Double.parseDouble(" 3.14 ") は半角空白を自動で無視して 3.14 を返しましたが、new BigDecimal(" 3.14") は例外になりました。メソッドごとに扱いが違うので、変換の前に自分で strip すると決めておくと迷いません。

全角数字はNFKCで半角にそろえる

parseInt は全角数字を読めますが、全角のマイナス - やカンマ ,、小数点 . は読めません。Normalizer.normalize(s, Normalizer.Form.NFKC) を通すと、数字・符号・カンマ・小数点がまとめて半角になります。

String s = Normalizer.normalize("123,456.7 -5 +8", Normalizer.Form.NFKC);
// 123,456.7 -5 +8

NFKC は全角の英数字や半角カナなども標準の形に変えます。数値の入力欄のように、数字だけが来る前提の値に使うのが向いています。

カンマ区切りはNumberFormatで読むか取り除く

"1,234" を読む方法は2つあります。

方法 コード 向いている場面
カンマを除く Integer.parseInt(s.replace(",", "")) 形式が決まっている値
NumberFormat NumberFormat.getInstance(Locale.JAPAN).parse(s) ロケールごとの書式に合わせたい

NumberFormat を使うときは、次の2点を押さえておきます。

  • parse("12abc") は例外にならず 12 を返します。 読めるところまで読んで止まる仕様のためです。全体が数値かを確かめるには ParsePosition を渡し、pos.getIndex() == s.length() を確認します(実行結果の「厳密チェック」)。
  • ロケールで区切り文字の意味が変わります。 "1.234,5" は Locale.GERMANY では 1234.5、Locale.JAPAN では 1.234 と読まれました。ロケールは既定値に任せず、明示して渡します。

また、NumberFormat が投げるのは NumberFormatException ではなく、検査例外の ParseException(Unparseable number: "abc")です。

小数はBigDecimalかdoubleで受ける

"3.14" や "1e3" は整数ではないので、parseInt では読めません。

目的 使うもの 結果の例
小数のまま正確に扱う new BigDecimal("3.14") 3.14
計算の速さを優先し、誤差を許容できる Double.parseDouble("3.14") 3.14
小数点以下を切り捨てて int にする new BigDecimal("3.14").intValue() 3
小数が混じっていたら気づきたい new BigDecimal("3.14").intValueExact() ArithmeticException: Rounding necessary

(int) Double.parseDouble("3.99") は 3 になります。切り捨てでよいのか、四捨五入したいのかを決めてから書くと、後で数字が合わないという事態を防げます。

桁あふれはLongやBigIntegerに切り替える

int に入るのは -2147483648 から 2147483647 までです。"2147483648" は数字だけの文字列ですが、範囲を1つ超えるので例外になります。10桁以上になりうる ID や数量は、最初から long で受けると安心です。

値の大きさ 使う型 変換
およそ ±21億まで int Integer.parseInt(s)
およそ ±922京まで long Long.parseLong(s)
それ以上 BigInteger new BigInteger(s)

long で読んでから int に収まるかを確かめたいときは Math.toIntExact(long) が使えます。収まらないときは ArithmeticException: integer overflow になります。符号なしの 32 ビット値を読む Integer.parseUnsignedInt("4294967295") は、int としては -1 になる点も実行結果で確かめました。

符号と16進表記は専用の読み方を使う

  • 先頭の + と - は1つだけ読めます。"--5" や "-" だけの文字列は例外になります。
  • 16進数は Integer.parseInt("1F", 16)(接頭辞なし)か Integer.decode("0x1F")(接頭辞あり)で読みます。parseInt("0x1F", 16) は x が16進の数字ではないため例外になります。
  • decode は 0x・0X・# を16進、先頭の 0 を8進として扱います(公式 API ドキュメントの記載と実行結果が一致)。先頭に 0 が付く10進の値("010" など)は decode に渡さないようにします。
  • ソースコードで書ける 1_000 や 42L は、文字列としては読めません。アンダースコアは replace("_", "") で除きます。

try-catchで受けて処理を続ける

入力が正しくないときに、メッセージを出して次へ進む基本形です。

import java.util.Scanner;

public class Catch {
    public static void main(String[] args) {
        String[] inputs = { "20", "二十", "" };
        for (String input : inputs) {
            try {
                int age = Integer.parseInt(input);
                System.out.println("年齢: " + age);
            } catch (NumberFormatException e) {
                System.out.println("数値として読めません: [" + input + "] / " + e.getMessage());
            }
        }
        // Scanner の nextInt は別の例外になる
        try {
            new Scanner("abc").nextInt();
        } catch (RuntimeException e) {
            System.out.println("Scanner.nextInt => " + e.getClass().getName() + ": " + e.getMessage());
        }
        System.out.println("NumberFormatException の親 = " + NumberFormatException.class.getSuperclass().getName());
    }
}

実行結果(Java 21.0.10。17.0.20.1・25.0.4.1 も同じ):

年齢: 20
数値として読めません: [二十] / For input string: "二十"
数値として読めません: [] / For input string: ""
Scanner.nextInt => java.util.InputMismatchException: null
NumberFormatException の親 = java.lang.IllegalArgumentException

書くときのポイントは3つです。

  • catch の中を空にしない。 入力値とメッセージをログに残すと、あとで原因をたどれます。
  • catch するのは NumberFormatException に絞る。 Exception でまとめて受けると、別の不具合まで隠れてしまいます。
  • Scanner の nextInt() は別の例外(java.util.InputMismatchException)を投げます。キーボード入力を受けるコードでは、こちらを catch します。

正規表現で事前にチェックする

変換の前に形を確かめておくと、例外に頼らず分岐できます。

チェックしたい形 正規表現 通る例
符号なしの整数 [0-9]+ 42
符号つきの整数 [+-]?[0-9]+ -5 +5
小数も可 [+-]?[0-9]+(\.[0-9]+)? 3.14 42
カンマ区切りの整数 [+-]?[0-9]{1,3}(,[0-9]{3})+ 1,234
16進数 [0-9a-fA-F]+ 1F

実行結果から、注意点を3つ挙げます。

  • 正規表現は桁あふれを判定できません。 "99999999999".matches("[+-]?[0-9]+") は true ですが、parseInt では例外になります。桁数を [0-9]{1,9} のように絞るか、try-catch と組み合わせます。
  • \d と [0-9] は、Java の既定では半角数字だけに一致します。 全角の "123" はどちらも false でした。
  • Character.isDigit は全角数字も true です。さらに、空文字に chars().allMatch(...) を使うと true が返るため、空文字の判定を別に入れる必要があります。

何度も呼ぶ場所では、String.matches の代わりに Pattern.compile した結果を static final の定数に持っておくと、毎回のコンパイルを省けます。

Optionalで返す安全な変換メソッド

ここまでの対処を1つにまとめたユーティリティです。正規化(NFKC と strip)→ 形のチェック → 変換 → 桁あふれは空で返す、という順に処理します。クラスごとコピーして使えます(Java 11 以降。確認は 17・21・25)。

import java.math.BigDecimal;
import java.text.Normalizer;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.regex.Pattern;

public final class SafeNumbers {
    private SafeNumbers() {}

    // 半角の整数(符号は任意)だけを通す
    private static final Pattern INT_PATTERN = Pattern.compile("[+-]?[0-9]+");

    /** 前後の空白(全角空白を含む)を取り、全角の数字・符号を半角にそろえる */
    public static String normalize(String s) {
        if (s == null) return null;
        return Normalizer.normalize(s, Normalizer.Form.NFKC).strip();
    }

    /** int に変換できれば値を、できなければ空を返す(例外は外に出さない) */
    public static OptionalInt toInt(String s) {
        String n = normalize(s);
        if (n == null || !INT_PATTERN.matcher(n).matches()) return OptionalInt.empty();
        try {
            return OptionalInt.of(Integer.parseInt(n));
        } catch (NumberFormatException e) {   // 桁あふれはここに来る
            return OptionalInt.empty();
        }
    }

    public static OptionalLong toLong(String s) {
        String n = normalize(s);
        if (n == null || !INT_PATTERN.matcher(n).matches()) return OptionalLong.empty();
        try {
            return OptionalLong.of(Long.parseLong(n));
        } catch (NumberFormatException e) {
            return OptionalLong.empty();
        }
    }

    /** 小数や桁の大きい値は BigDecimal で受ける。カンマ区切りは取り除く */
    public static Optional<BigDecimal> toBigDecimal(String s) {
        String n = normalize(s);
        if (n == null || n.isEmpty()) return Optional.empty();
        try {
            return Optional.of(new BigDecimal(n.replace(",", "")));
        } catch (NumberFormatException e) {
            return Optional.empty();
        }
    }

    /** 既定値つきの短い書き方 */
    public static int toIntOrDefault(String s, int defaultValue) {
        return toInt(s).orElse(defaultValue);
    }

    public static void main(String[] args) {
        System.out.println("java.version = " + System.getProperty("java.version"));
        String[] inputs = { "42", "", null, " 42 ", " " + "42", "123", "-5", "1,234", "3.14", "2147483648", "abc", "0x1F" };
        System.out.println("入力 | toInt | toLong | toBigDecimal");
        for (String s : inputs) {
            String shown = s == null ? "null" : "\"" + s + "\"";
            System.out.println(shown + " | " + toInt(s) + " | " + toLong(s) + " | " + toBigDecimal(s));
        }
        System.out.println("toIntOrDefault(\"abc\", 0) = " + toIntOrDefault("abc", 0));
        System.out.println("toIntOrDefault(\"123\", 0) = " + toIntOrDefault("123", 0));

    }
}

実行結果(Java 21.0.10。Java 17.0.20.1・25.0.4.1 は1行目以外同じ):

java.version = 21.0.10
入力 | toInt | toLong | toBigDecimal
"42" | OptionalInt[42] | OptionalLong[42] | Optional[42]
"" | OptionalInt.empty | OptionalLong.empty | Optional.empty
null | OptionalInt.empty | OptionalLong.empty | Optional.empty
" 42 " | OptionalInt[42] | OptionalLong[42] | Optional[42]
" 42" | OptionalInt[42] | OptionalLong[42] | Optional[42]
"123" | OptionalInt[123] | OptionalLong[123] | Optional[123]
"-5" | OptionalInt[-5] | OptionalLong[-5] | Optional[-5]
"1,234" | OptionalInt.empty | OptionalLong.empty | Optional[1234]
"3.14" | OptionalInt.empty | OptionalLong.empty | Optional[3.14]
"2147483648" | OptionalInt.empty | OptionalLong[2147483648] | Optional[2147483648]
"abc" | OptionalInt.empty | OptionalLong.empty | Optional.empty
"0x1F" | OptionalInt.empty | OptionalLong.empty | Optional.empty
toIntOrDefault("abc", 0) = 0
toIntOrDefault("123", 0) = 123

設計上のポイントは次のとおりです。

  • int には OptionalInt、long には OptionalLong を使うと、Integer への箱詰めを挟まずに済みます。
  • "1,234" は toInt では空、toBigDecimal では 1234 としています。カンマを許すかどうかは用途で変わるので、整数版では形を厳しめにしました。許したい場合は normalize の後に replace(",", "") を足します。
  • "0x1F" は空になります。16進を受けたい入力は、別のメソッド(Integer.decode)に分けるほうが見通しがよくなります。

どの方法を選ぶかの判断表

状況 選ぶ方法
人が入力した値を受ける 正規化+Optional 版の変換(上の SafeNumbers)
形式が保証された値(自分で書き出した数値など) Integer.parseInt をそのまま使う
読めないときは処理を止めたい try-catch で受け、入力値を添えた例外を投げ直す
読めないときは既定値でよい toIntOrDefault(s, 0)
カンマ区切り・ロケール依存の書式 NumberFormat + ParsePosition
小数・桁数が大きい BigDecimal / BigInteger
16進・8進の表記 Integer.parseInt(s, 16) / Integer.decode(s)

Javaのバージョン別に確認した結果

確認した内容 8(1.8.0_504) 17.0.20.1 21.0.10 25.0.4.1
早見表の「読める・読めない」 同じ 同じ 同じ 同じ
parseInt(null) のメッセージ null Cannot parse null string 同左 同左
基数つきメッセージの under radix なし あり あり あり
NumberChecks・Catch の出力 未実行 同じ 基準 同じ
SafeNumbers の出力 未実行 同じ 基準 同じ

Java 8 では strip() と isBlank() が使えないため、NumberChecks と SafeNumbers は動かしていません。Java 9〜16 と 22〜24 も未実行です。

よくある質問

Q. 数字しか入れていないのに NumberFormatException になります。なぜですか? A. 多いのは、前後に空白や改行が付いている、値が int の範囲を超えている、空文字が渡されている、の3つです。メッセージの引用符の中を角かっこ付きでログに出すと見分けられます。空白なら strip()、桁数なら Long.parseLong で解決します。

Q. try-catch と事前チェックは、どちらがよいですか? A. 併用がおすすめです。形のチェックで大半を分け、桁あふれのように正規表現で判定しにくいものを try-catch で受けます。記事中の SafeNumbers はこの組み合わせです。

Q. 全角数字が読めてしまうのを避けたいときは? A. 変換の前に [0-9]+ の正規表現で確かめます。Java の既定では [0-9] も \d も半角数字だけに一致するので、全角数字はここで分けられます。

まとめ

  • NumberFormatException は「数値として読めない文字列」が渡された合図。メッセージの引用符の中身が、読めなかった値そのものです。
  • 空文字は For input string: ""、null は Cannot parse null string(Java 8 では null)と表示されます。
  • 前後の空白は strip()、全角は NFKC、カンマは replace か NumberFormat で整えてから変換します。
  • 小数は BigDecimal、桁あふれは long や BigInteger に切り替えます。
  • 16進は parseInt(s, 16) か decode。decode は先頭の 0 を8進として扱います。
  • 正規表現だけでは桁あふれを判定できないので、try-catch と組み合わせます。
  • Optional で返す変換メソッドを1つ用意しておくと、呼び出し側の分岐がそろいます。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Java NumberFormatExceptionの原因と対処|数値チェック」

株式会社RJCでは、Javaの例外の読み方や数値変換の書き方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?