- JAVA
StringIndexOutOfBoundsExceptionの直し方|原因一覧
java.lang.StringIndexOutOfBoundsException は、文字列の長さの外の位置(インデックス)を指定したときの例外です。 位置は0から数えるので、長さ5の文字列で使える位置は0〜4です。Java 21 では、メッセージは2つの形のどちらかで出ます。
java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
java.lang.StringIndexOutOfBoundsException: Range [0, 6) out of bounds for length 5
| メッセージの形 | 意味 | 主なメソッド |
|---|---|---|
Index 〈i〉 out of bounds for length 〈n〉 |
1つの位置 i が 0〜n-1 の外 | charAt・codePointAt・deleteCharAt・setCharAt |
Range [〈開始〉, 〈終了〉) out of bounds for length 〈n〉 |
範囲が 0〜n の外、または開始 > 終了 | substring・subSequence・delete・replace・insert |
Range [〈開始〉, 〈開始〉 + 〈個数〉) out of bounds for length 〈n〉 |
配列の一部を指定する引数が外 | String.valueOf(char[], 開始, 個数)・new String(char[], …) |
直し方は、メッセージの数字(位置と length)を見て、「なぜその位置になったか」を突き止めることです。 多い原因は、indexOf が -1 を返した、空文字だった、文字数とバイト数を取り違えた、絵文字などで length() が見た目の文字数と違った、の4つです。
確認環境:OpenJDK 21.0.10(Linux)で、下に載せたメソッドのメッセージを実際に出して確かめました。版ごとの違いは、OpenJDK 1.8.0_504・11.0.32.1・17.0.20.1・21.0.10・25.0.4.1 で同じプログラムを動かして比べました(確認日 2026年10月12日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「StringIndexOutOfBoundsExceptionの直し方|原因一覧」
メッセージの読み方
Index の形(1つの位置)
Index 5 out of bounds for length 5
長さ(length)5 の文字列で、位置5を指定したという意味です。使える位置は0〜4なので、5は1つはみ出しています。「長さと同じ位置」を指定するのは、ループの条件を <= にした、length() をそのまま位置に使った、のどちらかが多いです。
Range の形(範囲)
Range [0, 6) out of bounds for length 5
[0, 6) は「0以上6未満」の範囲です。substring(開始, 終了) の終了は含まれないため、) で書かれています。範囲で使える条件は次のとおりです。
| 条件 | 例(length 5) | 結果 |
|---|---|---|
| 0 ≦ 開始 ≦ 終了 ≦ 長さ | substring(2, 5) |
OK("llo") |
| 開始 = 終了 = 長さ | substring(5) |
OK(空文字) |
| 終了 > 長さ | substring(2, 10) → Range [2, 10) |
例外 |
| 開始 > 終了 | substring(3, 1) → Range [3, 1) |
例外 |
| 開始 < 0 | substring(-1) → Range [-1, 5) |
例外 |
substring(開始) は substring(開始, length()) として扱われます。 そのため "Hello".substring(6) は Range [6, 5) と、終了が5のメッセージになりました。
スタックトレースで、自分のコードの行を探す
Java 21 で "A-123".substring(0, 6) を実行したときのスタックトレースです。
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: Range [0, 6) out of bounds for length 5
at java.base/jdk.internal.util.Preconditions$1.apply(Preconditions.java:55)
at java.base/jdk.internal.util.Preconditions$1.apply(Preconditions.java:52)
at java.base/jdk.internal.util.Preconditions$4.apply(Preconditions.java:213)
at java.base/jdk.internal.util.Preconditions$4.apply(Preconditions.java:210)
at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:98)
at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckFromToIndex(Preconditions.java:112)
at java.base/jdk.internal.util.Preconditions.checkFromToIndex(Preconditions.java:349)
at java.base/java.lang.String.checkBoundsBeginEnd(String.java:4855)
at java.base/java.lang.String.substring(String.java:2823)
at Main.code(Main.java:2)
at Main.main(Main.java:3)
java.base/ で始まる行は JDK の中なので飛ばし、最初に出る自分のクラスの行(Main.code(Main.java:2))が原因の行です。 その1つ上の行(String.substring)で、どのメソッドを呼んだかが分かります。Java 8 では JDK の中の行が1行だけでした。
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 6
at java.lang.String.substring(String.java:1963)
at Main.code(Main.java:2)
at Main.main(Main.java:3)
メソッド別のメッセージ一覧(Java 21 で実行)
"Hello"(長さ5)などに、範囲外の位置を渡して出たメッセージです。
String のメソッド
| 呼び出し | 例外 | メッセージ |
|---|---|---|
"Hello".charAt(5) |
SIOOBE | Index 5 out of bounds for length 5 |
"Hello".charAt(-1) |
SIOOBE | Index -1 out of bounds for length 5 |
"".charAt(0) |
SIOOBE | Index 0 out of bounds for length 0 |
"Hello".substring(6) |
SIOOBE | Range [6, 5) out of bounds for length 5 |
"Hello".substring(2, 10) |
SIOOBE | Range [2, 10) out of bounds for length 5 |
"Hello".substring(3, 1) |
SIOOBE | Range [3, 1) out of bounds for length 5 |
"Hello".subSequence(1, 9) |
SIOOBE | Range [1, 9) out of bounds for length 5 |
"Hello".codePointAt(5) |
SIOOBE | Index 5 out of bounds for length 5 |
"Hello".codePointBefore(0) |
SIOOBE | Index -1 out of bounds for length 5 |
"Hello".getChars(0, 9, buf, 0) |
SIOOBE | Range [0, 9) out of bounds for length 5 |
"Hello".offsetByCodePoints(0, 9) |
IOOBE | null(メッセージなし) |
String.valueOf(new char[3], 1, 5) |
SIOOBE | Range [1, 1 + 5) out of bounds for length 3 |
new String(new char[3], 2, 5) |
SIOOBE | Range [2, 2 + 5) out of bounds for length 3 |
new String(new byte[3], 0, 5, UTF_8) |
SIOOBE | Range [0, 0 + 5) out of bounds for length 3 |
SIOOBE は StringIndexOutOfBoundsException、IOOBE は IndexOutOfBoundsException です。
StringBuilder のメソッド
呼び出し(中身は "Hello") |
例外 | メッセージ |
|---|---|---|
sb.charAt(5) |
SIOOBE | Index 5 out of bounds for length 5 |
sb.deleteCharAt(5) |
SIOOBE | Index 5 out of bounds for length 5 |
sb.setCharAt(5, 'x') |
SIOOBE | Index 5 out of bounds for length 5 |
sb.insert(6, "x") |
SIOOBE | Range [6, 5) out of bounds for length 5 |
sb.substring(6) |
SIOOBE | Range [6, 5) out of bounds for length 5 |
sb.delete(3, 1) |
SIOOBE | Range [3, 1) out of bounds for length 5 |
sb.delete(6, 9) |
SIOOBE | Range [6, 5) out of bounds for length 5 |
sb.replace(6, 7, "x") |
SIOOBE | Range [6, 5) out of bounds for length 5 |
sb.setLength(-1) |
SIOOBE | String index out of range: -1 |
new StringBuilder().append(new char[3], 1, 5) |
IOOBE | Range [1, 6) out of bounds for length 3 |
new StringBuilder().append("abc", 1, 5) |
IOOBE | Range [1, 5) out of bounds for length 3 |
例外にならないもの
| 呼び出し | 結果 | 理由 |
|---|---|---|
"Hello".substring(5) |
""(空文字) |
開始=長さは範囲内 |
sb.delete(2, 99) |
"He" |
delete の終了は長さで切り詰められる |
"Hello".indexOf('l', 9) |
-1 |
検索は範囲外でも例外にしない |
"Hello".regionMatches(9, "x", 0, 1) |
false |
範囲外は「一致しない」扱い |
delete の終了は長すぎても例外になりませんが、substring の終了は例外になります。 同じ「範囲」の引数でも、メソッドによって扱いが違います。
Java の版ごとのメッセージの違い
同じプログラムを5つの JDK で動かした結果です。Java 17 から 21 の間で、「Index … out of bounds」「Range […) out of bounds」の形に揃いました。 25 は 21 と同じでした。
| 呼び出し | Java 8 | Java 11 | Java 17 | Java 21・25 |
|---|---|---|---|---|
"Hello".substring(6) |
String index out of range: -1 |
String index out of range: -1 |
begin 6, end 5, length 5 |
Range [6, 5) out of bounds for length 5 |
"Hello".substring(2, 10) |
String index out of range: 10 |
begin 2, end 10, length 5 |
begin 2, end 10, length 5 |
Range [2, 10) out of bounds for length 5 |
"Hello".substring(3, 1) |
String index out of range: -2 |
begin 3, end 1, length 5 |
begin 3, end 1, length 5 |
Range [3, 1) out of bounds for length 5 |
"Hello".charAt(5) |
String index out of range: 5 |
String index out of range: 5 |
String index out of range: 5 |
Index 5 out of bounds for length 5 |
"あいうえお".charAt(5) |
String index out of range: 5 |
index 5,length 5 |
index 5, length 5 |
Index 5 out of bounds for length 5 |
sb.deleteCharAt(5) |
String index out of range: 5 |
index 5,length 5 |
index 5, length 5 |
Index 5 out of bounds for length 5 |
sb.delete(3, 1) |
null |
start 3, end 1, length 5 |
start 3, end 1, length 5 |
Range [3, 1) out of bounds for length 5 |
sb.append(new char[3], 1, 5) |
ArrayIndexOutOfBoundsException: null |
IOOBE: start 1, end 6, length 3 |
IOOBE: start 1, end 6, length 3 |
IOOBE: Range [1, 6) out of bounds for length 3 |
読み取れることは3つです。
- Java 8 の
substring(6)の-1は「位置 -1」ではありません。 「残りの長さ(5 - 6)」が -1 という意味でした。Java 8 のログを読むときは注意します。 - Java 11・17 では、同じ
charAt(5)でも、英数字だけの文字列と日本語を含む文字列でメッセージが違いました。 文字列の内部の持ち方(1バイトか2バイトか)で、通るコードが分かれていたためです。 - 版を上げるとメッセージの文面が変わるため、ログ監視でメッセージの文字列を条件にしている場合は、条件を見直します。 例外のクラス名で判定すると版の影響を受けません。
原因別の直し方
| 原因 | よくあるコード | メッセージの特徴 |
|---|---|---|
indexOf が見つからず -1 |
s.substring(0, s.indexOf(",")) |
Range [0, -1) |
| 文字列が短い・空 | s.substring(s.length() - 3) |
Range [-1, 2)・length 0 |
ループの条件が <= |
for (i = 0; i <= s.length(); i++) s.charAt(i) |
Index n … for length n |
| バイト数を文字数として使った | s.substring(0, s.getBytes(UTF_8).length) |
終了が長さの数倍 |
| 別の文字列で求めた位置を使った | 変換前の位置を変換後に使う | 長さが変換前より短い |
| 絵文字・𠮷 などで数がずれた | 見た目の文字数で位置を計算 | 位置が1〜数個だけはみ出す |
indexOf の -1 を確かめる
indexOf は、見つからないと -1 を返します。Java 21 での結果です。
String s = "abc";
s.substring(0, s.indexOf(",")); // Range [0, -1) out of bounds for length 3
s.substring(s.indexOf(":") + 1); // "abc"(-1 + 1 = 0 なので例外にならない)
後ろの書き方は例外になりませんが、「見つからなかった」ことに気づけません。 見つからない場合をはっきり分けます。
int pos = s.indexOf(",");
String head = (pos >= 0) ? s.substring(0, pos) : s; // 見つからなければ全体
短い文字列・空文字を先に分ける
"ab".substring("ab".length() - 3); // Range [-1, 2) out of bounds for length 2
"".charAt(0); // Index 0 out of bounds for length 0
// 末尾3文字(短ければ全体)
String tail = s.length() <= 3 ? s : s.substring(s.length() - 3);
// 先頭1文字(空なら空)
String first = s.isEmpty() ? "" : s.substring(0, 1);
末尾の切り出しや substring の書き方の詳細は、関連記事「Java substringの使い方 末尾切り出しと範囲外エラーの直し方」で扱っています。null と空文字の判定は、関連記事「Java文字列の空判定|isEmptyとisBlankの違い早見表」で扱っています。
ループは < length() にする
for (int i = 0; i < s.length(); i++) { // <= にすると最後に Index n … for length n
char c = s.charAt(i);
}
文字を1つずつ見るだけなら、位置を使わない書き方にすると、この種の例外は起きません。
for (char c : s.toCharArray()) { … }
s.codePoints().forEach(cp -> { … }); // 絵文字も1文字として扱う(下で説明)
別の文字列で求めた位置を使わない
全角・半角の変換(正規化)や大文字・小文字の変換で、文字列の長さが変わることがあります。Java 21 で確かめた例です。
String kana = "ガギグ"; // 半角カナ+濁点で length 6
String norm = Normalizer.normalize(kana, Normalizer.Form.NFKC); // "ガギグ" で length 3
norm.charAt(kana.length() - 1); // Index 5 out of bounds for length 3
位置は、実際に切り出す文字列で計算し直します。 変換と切り出しを同じ文字列に対して行うのが基本です。
全角・絵文字(サロゲートペア)でずれる
length() は「見た目の文字数」ではない
Java の String は UTF-16(1文字を2バイトの単位で表す方式)で文字を持ち、length() はその単位(char)の数を返します。絵文字や「𠮷」などは2つの char(サロゲートペア)で1文字になります。 Java 21 で数えた結果です。
| 文字列 | length() |
コードポイント数 | 見た目の文字数(書記素) | UTF-8 のバイト数 |
|---|---|---|---|---|
寿司 |
2 | 2 | 2 | 6 |
🍣寿司 |
4 | 3 | 3 | 10 |
𠮷野家 |
4 | 3 | 3 | 10 |
👨👩👧(家族の絵文字) |
8 | 5 | 1 | 18 |
ガギ(半角カナ+濁点) |
4 | 4 | 2 | 12 |
- コードポイント:Unicode で1文字に付いた番号。
codePointCountで数える - 書記素(グラフィーム):人が1文字と感じる単位。
BreakIteratorで数える
substring(0, 1) で絵文字を切ると、例外にはならず、壊れた半分の文字になります。 Java 21 では、"🍣寿司".substring(0, 1) は長さ1、中身は U+D83C(サロゲートペアの前半だけ)でした。例外にならない分、画面や DB で文字化けとして見つかることが多い問題です。
コードポイント単位で切り出す
// 先頭 n 文字(コードポイント単位)。短ければ全体を返す
static String leftCodePoints(String s, int n) {
if (s.codePointCount(0, s.length()) <= n) {
return s;
}
return s.substring(0, s.offsetByCodePoints(0, n));
}
leftCodePoints("🍣寿司が好き", 3); // "🍣寿司"
leftCodePoints("ab", 3); // "ab"
| やりたいこと | char 単位(ずれる) | コードポイント単位 |
|---|---|---|
| 文字数 | s.length() |
s.codePointCount(0, s.length()) |
| i 番目の文字 | s.charAt(i) |
s.codePointAt(s.offsetByCodePoints(0, i)) |
| 先頭 n 文字 | s.substring(0, n) |
s.substring(0, s.offsetByCodePoints(0, n)) |
| 1文字ずつ | s.toCharArray() |
s.codePoints() |
offsetByCodePoints は、範囲を超えると IndexOutOfBoundsException(メッセージなし)になりました。上のように、先に codePointCount で長さを確かめてから呼びます。
家族の絵文字(👨👩👧)のように、複数のコードポイントで1文字に見えるものまで1文字として扱うには、java.text.BreakIterator.getCharacterInstance() を使います。Java 21 では、👨👩👧 は1文字、ガギ は2文字と数えました。
サロゲートペアのしくみは、関連記事「UnicodeとUTF-8の違いとは?UTF-16・サロゲートペアまで実行結果で理解」で扱っています。
バイト数で切り詰めるとき
DB の列が「10バイトまで」のときに、getBytes().length を文字の位置として使うと例外になります。
String name = "山田太郎"; // length 4、UTF-8 で 12 バイト
name.substring(0, name.getBytes(UTF_8).length - 2); // Range [0, 10) out of bounds for length 4
バイト数で切るときは、1文字ずつバイト数を足していき、超える手前で止めます。
static String leftBytes(String s, int maxBytes) {
StringBuilder sb = new StringBuilder();
int bytes = 0;
for (int i = 0; i < s.length(); ) {
int cp = s.codePointAt(i);
int len = new String(Character.toChars(cp)).getBytes(StandardCharsets.UTF_8).length;
if (bytes + len > maxBytes) {
break;
}
sb.appendCodePoint(cp);
bytes += len;
i += Character.charCount(cp);
}
return sb.toString();
}
leftBytes("山田太郎🍣", 10); // "山田太"(9バイト)
leftBytes("🍣寿司", 5); // "🍣"(4バイト)
コードポイント単位で進めるので、絵文字を半分で切ることもありません。MySQL で絵文字を保存するときのエラーは、関連記事「MySQL Incorrect string valueの直し方|絵文字」で扱っています。
catch するなら IndexOutOfBoundsException
StringIndexOutOfBoundsException は IndexOutOfBoundsException の子クラスです。上の一覧のとおり、StringBuilder.append(char[], …) や offsetByCodePoints は親の IndexOutOfBoundsException を投げるため、catch (StringIndexOutOfBoundsException e) では受け止められません。
| 例外 | 親 | 主に出る場所 |
|---|---|---|
StringIndexOutOfBoundsException |
IndexOutOfBoundsException |
String・StringBuilder の位置の指定 |
ArrayIndexOutOfBoundsException |
IndexOutOfBoundsException |
配列の添字(Java 8 の sb.append(char[], …) もこれ) |
IndexOutOfBoundsException |
RuntimeException |
List.get・StringBuilder.append(char[], …) など |
ただし、範囲外の例外は「事前に確かめれば防げる」種類の例外です。try-catch で包むより、上の直し方で位置を正しく計算するほうが、原因が残りません。配列の場合は関連記事「Java ArrayIndexOutOfBoundsException原因と対処」、リストの場合は関連記事「Index 0 out of bounds for length 0の直し方」で扱っています。
よくある質問
Q. String index out of range: -1 と出ます。どこで -1 を渡したのか分かりません。
A. Java 8 の substring(開始) では、-1 は「残りの長さ」のことがあります。開始が長さより1大きいときにこの値になります。Java 17 以降なら begin 6, end 5 や Range [6, 5) のように開始と終了が両方出るので、版を確かめてから読み分けます。
Q. 本番だけで起きて、手元では起きません。 A. 入力の文字列が違う可能性が高いです。空文字、区切り文字が無い行、想定より短いコード、絵文字を含む名前などを、テストデータに足して確かめます。境界値の考え方は、関連記事「同値分割・境界値分析|テストケース表とpytestコード例で解説」で扱っています。
Q. 例外一覧の中で、この例外はどこに入りますか。
A. 実行時例外(RuntimeException)の一種で、throws の宣言は要りません。ほかの例外との関係は、関連記事「Java 例外一覧|種類・原因・対処の早見表と使い分け」で扱っています。
まとめ
- 文字列の長さの外の位置を指定したときの例外。位置は0〜長さ-1、範囲の終了は長さまで
- Java 21 のメッセージは
Index i out of bounds for length nとRange [開始, 終了) out of bounds for length nの2形式 - Java 8・11・17 では文面が違う。Java 8 の
-1は「残りの長さ」のことがある - 多い原因は
indexOfの -1、空・短い文字列、<=のループ、バイト数との取り違え - 絵文字・𠮷 は
length()で2と数える。切り出しはoffsetByCodePoints・codePoints()で StringBuilder.append(char[], …)などは 親のIndexOutOfBoundsExceptionを投げる
参考・出典
確認日はいずれも 2026年10月12日です。
- Java SE 21 API「StringIndexOutOfBoundsException」:https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/StringIndexOutOfBoundsException.html
- Java SE 21 API「String」:https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/String.html
- Java SE 21 API「StringBuilder」:https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/StringBuilder.html
- Java SE 21 API「Character」(Unicode Character Representations):https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html
- Java SE 21 API「BreakIterator」:https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/text/BreakIterator.html
- OpenJDK jdk21u ソースコード「String.java」:https://github.com/openjdk/jdk21u/blob/master/src/java.base/share/classes/java/lang/String.java
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「StringIndexOutOfBoundsExceptionの直し方|原因一覧」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。