- JAVA
Java IOExceptionの原因と対処|種類一覧とエラー文別の直し方
IOExceptionとは、ファイルの読み書きやネットワーク通信など、プログラムの外とのやり取り(入出力)がうまくいかなかったときに出る例外です。 IOException そのものよりも、NoSuchFileException や ConnectException といった子クラスの名前とエラー文を見れば、原因はほぼ特定できます。
この記事では、ファイル・ストリーム・ネットワークの入出力エラーを Linux 上で実際に再現し、エラー文・原因・直し方を一覧表にしました。あわせて、Files.lines やラムダ式の中で出る UncheckedIOException の正体、ラムダの中で IOException を包んで外で取り出す書き方、catch を書く順番とコンパイルエラーの例まで、コピペできるコードで説明します。
確認環境:Ubuntu 24.04 / OpenJDK 21.0.10。ファイルの権限に関する例は一般ユーザーで実行しています。
IOExceptionの早見表(エラー文から原因と直し方を引く)
| エラー文(クラス名: メッセージ) | 原因 | 直し方 |
|---|---|---|
java.nio.file.NoSuchFileException: work/none.txt |
ファイル、または途中のフォルダがない | パスを確かめる。書き込みなら先に Files.createDirectories(親フォルダ) |
java.io.FileNotFoundException: work/none.txt (No such file or directory) |
同上(FileInputStream など java.io 側の表示) |
同上。かっこの中の理由を読む |
java.nio.file.AccessDeniedException: secret.txt |
読み書きの権限がない(java.io 側では FileNotFoundException: secret.txt (Permission denied)) |
ls -l で権限と所有者を確かめ、実行ユーザーか権限を合わせる |
java.nio.file.FileAlreadyExistsException: work/a.txt |
作成・コピー先がすでにある | 上書きするなら StandardCopyOption.REPLACE_EXISTING。フォルダは createDirectories |
java.nio.file.DirectoryNotEmptyException: work/full |
中身のあるフォルダを削除しようとした | 中のファイルから先に削除する |
java.io.IOException: Is a directory |
フォルダをファイルとして読んだ | Files.isRegularFile(path) で確かめる |
java.nio.file.NotDirectoryException: work/a.txt |
ファイルをフォルダとして開いた | Files.isDirectory(path) で確かめる |
java.nio.charset.MalformedInputException: Input length = 1 |
ファイルの文字コードが、読み取りに指定したもの(既定は UTF-8)と違う | 実際の文字コードを指定する(例:Charset.forName("Shift_JIS")) |
java.io.IOException: Stream closed |
閉じた後のストリームを使った | 読み書きは try-with-resources のブロックの中で済ませる |
java.io.EOFException |
決まった長さを読む途中でデータが終わった | データの長さ・形式を確かめる。終わりの判定は戻り値(-1 や null)で行う |
java.io.IOException: No space left on device |
書き込み先の空き容量がない | df -h で空きを確かめる |
java.net.UnknownHostException: no-such-host.invalid: Name or service not known |
ホスト名から接続先を引けない | ホスト名のつづり、DNS の設定を確かめる |
java.net.ConnectException: Connection refused |
接続先のポートで誰も待ち受けていない | 相手のサービスが起動しているか、ポート番号が合っているか確かめる |
java.net.SocketTimeoutException: Read timed out |
設定した時間内に応答が来なかった(HttpClient では HttpTimeoutException: request timed out) |
相手の処理時間を確かめ、待つ時間の設定を見直す |
java.net.SocketException: Connection reset |
通信の途中で相手側から切断された | 相手側のログを確かめる。必要なら接続し直す |
java.net.BindException: Address already in use |
待ち受けようとしたポートが使用中 | 別のポートにする。前のプロセスが残っていないか確かめる |
java.io.UncheckedIOException: java.nio.charset.MalformedInputException: ... |
Stream の処理中に起きた IOException が包まれて届いた | getCause() で中身の IOException を取り出して上の行を引く |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java IOExceptionの原因と対処|種類一覧とエラー文別の直し方」
IOExceptionとは:検査例外と子クラスの関係
IOException は java.io パッケージの例外で、Exception の直接の子クラスです。Java 21 の API ドキュメントでは「何らかの入出力例外が発生したことを示す。入出力処理がうまくいかなかった、または中断されたときに出る例外の一般的なクラス」と説明されています(出典は記事末尾)。
- 検査例外です。IOException を投げる可能性のあるメソッドを呼ぶときは、
try-catchで受けるか、メソッドにthrows IOExceptionを書く必要があります - 多くの子クラスがあります。API ドキュメントには直接の子クラスだけで35個が載っています
- 原因はプログラムの外(ファイルの有無、権限、ネットワーク、相手のサーバー)にあることが多く、コードが正しくても起こりえます
try-catch も throws も書かずに呼ぶと、javac が error: unreported exception IOException; must be caught or declared to be thrown と知らせます。
種類一覧:よく出会う子クラス
親子関係は、各クラスの親クラスを実際にたどって確かめたものです。
| 分野 | クラス | 親クラス | どんなとき |
|---|---|---|---|
| ファイル(java.io) | FileNotFoundException |
IOException |
FileInputStream、FileReader などで開けなかった |
| ファイル(java.nio.file) | NoSuchFileException |
FileSystemException |
ファイルやフォルダがない |
| ファイル(java.nio.file) | AccessDeniedException |
FileSystemException |
権限がない |
| ファイル(java.nio.file) | FileAlreadyExistsException |
FileSystemException |
すでにある |
| ファイル(java.nio.file) | DirectoryNotEmptyException |
FileSystemException |
フォルダが空ではない |
| ファイル(java.nio.file) | NotDirectoryException |
FileSystemException |
フォルダではない |
| 読み取り | EOFException |
IOException |
読んでいる途中でデータが終わった |
| 文字コード | MalformedInputException |
CharacterCodingException |
バイト列を文字にできない |
| ネットワーク | UnknownHostException |
IOException |
ホスト名を引けない |
| ネットワーク | ConnectException |
SocketException |
接続できない |
| ネットワーク | BindException |
SocketException |
ポートを確保できない |
| ネットワーク | SocketTimeoutException |
InterruptedIOException |
時間内に応答がない |
| ネットワーク | HttpTimeoutException |
IOException |
HttpClient で時間内に応答がない |
| (別枠) | UncheckedIOException |
RuntimeException |
IOException を包んだ非検査例外。IOException の子ではない |
FileSystemException、CharacterCodingException、SocketException、InterruptedIOException はいずれも IOException の子クラスです。つまり表の UncheckedIOException 以外は、すべて catch (IOException e) で受け取れます。
ファイル操作のエラーを再現する
次のプログラムは、うまくいかない操作をわざと並べ、例外のクラス名とメッセージを表示します。権限の例のために、実行するフォルダに「実行ユーザーが読み書きできないファイル secret.txt」と「実行ユーザーが書き込めないフォルダ locked」を用意しました(管理者が作成し、secret.txt は権限 600、locked は権限 755)。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class FileErrors {
interface Action { void run() throws IOException; }
static void show(String label, Action action) {
try {
action.run();
System.out.println(label + "\n => 例外なし");
} catch (IOException e) {
System.out.println(label + "\n => " + e.getClass().getName() + ": " + e.getMessage());
}
}
public static void main(String[] args) throws IOException {
// 準備:work フォルダの中に確認用のファイルを作る
Path work = Path.of("work");
Files.createDirectories(work.resolve("full"));
Files.writeString(work.resolve("a.txt"), "hello");
Files.writeString(work.resolve("full/child.txt"), "x");
Files.write(work.resolve("sjis.txt"), "こんにちは".getBytes("Shift_JIS"));
System.out.println("--- ファイルやフォルダが見つからない");
show("Files.readString(work/none.txt)", () -> Files.readString(work.resolve("none.txt")));
show("Files.writeString(work/nodir/b.txt)", () -> Files.writeString(work.resolve("nodir/b.txt"), "x"));
show("new FileInputStream(work/none.txt)", () -> new FileInputStream("work/none.txt").close());
System.out.println("--- すでにある・空ではない");
show("Files.createFile(work/a.txt)", () -> Files.createFile(work.resolve("a.txt")));
show("Files.copy(a.txt → full/child.txt)", () -> Files.copy(work.resolve("a.txt"), work.resolve("full/child.txt")));
show("Files.delete(work/full)", () -> Files.delete(work.resolve("full")));
System.out.println("--- ファイルとフォルダの取り違え");
show("Files.readString(work/full)", () -> Files.readString(work.resolve("full")));
show("Files.list(work/a.txt)", () -> Files.list(work.resolve("a.txt")).close());
show("new FileInputStream(work/full)", () -> new FileInputStream("work/full").close());
System.out.println("--- 権限がない");
show("Files.readString(secret.txt)", () -> Files.readString(Path.of("secret.txt")));
show("Files.writeString(secret.txt)", () -> Files.writeString(Path.of("secret.txt"), "x"));
show("new FileInputStream(secret.txt)", () -> new FileInputStream("secret.txt").close());
show("new FileOutputStream(secret.txt)", () -> new FileOutputStream("secret.txt").close());
show("Files.createFile(locked/new.txt)", () -> Files.createFile(Path.of("locked/new.txt")));
System.out.println("--- 文字コード・空き容量");
show("Files.readString(work/sjis.txt) を UTF-8 で", () -> Files.readString(work.resolve("sjis.txt")));
show("/dev/full に書き込む", () -> Files.writeString(Path.of("/dev/full"), "x"));
}
}
--- ファイルやフォルダが見つからない
Files.readString(work/none.txt)
=> java.nio.file.NoSuchFileException: work/none.txt
Files.writeString(work/nodir/b.txt)
=> java.nio.file.NoSuchFileException: work/nodir/b.txt
new FileInputStream(work/none.txt)
=> java.io.FileNotFoundException: work/none.txt (No such file or directory)
--- すでにある・空ではない
Files.createFile(work/a.txt)
=> java.nio.file.FileAlreadyExistsException: work/a.txt
Files.copy(a.txt → full/child.txt)
=> java.nio.file.FileAlreadyExistsException: work/full/child.txt
Files.delete(work/full)
=> java.nio.file.DirectoryNotEmptyException: work/full
--- ファイルとフォルダの取り違え
Files.readString(work/full)
=> java.io.IOException: Is a directory
Files.list(work/a.txt)
=> java.nio.file.NotDirectoryException: work/a.txt
new FileInputStream(work/full)
=> java.io.FileNotFoundException: work/full (Is a directory)
--- 権限がない
Files.readString(secret.txt)
=> java.nio.file.AccessDeniedException: secret.txt
Files.writeString(secret.txt)
=> java.nio.file.AccessDeniedException: secret.txt
new FileInputStream(secret.txt)
=> java.io.FileNotFoundException: secret.txt (Permission denied)
new FileOutputStream(secret.txt)
=> java.io.FileNotFoundException: secret.txt (Permission denied)
Files.createFile(locked/new.txt)
=> java.nio.file.FileAlreadyExistsException: locked/new.txt
--- 文字コード・空き容量
Files.readString(work/sjis.txt) を UTF-8 で
=> java.nio.charset.MalformedInputException: Input length = 1
/dev/full に書き込む
=> java.io.IOException: No space left on device
結果から読み取れるポイントです。
Filesのメソッド(java.nio.file)は、原因ごとに別のクラスを投げます。クラス名だけで「ない」「権限がない」「すでにある」が区別できますFileInputStreamなど java.io のクラスは、どの原因でも FileNotFoundException です。原因はメッセージのかっこの中(No such file or directory、Permission denied、Is a directory)で見分けますFiles.writeString(work/nodir/b.txt)のように、書き込みでも NoSuchFileException が出ます。途中のフォルダがないためです- 権限の例は、管理者(root)で実行すると権限の確認を通過して「例外なし」になります。開発環境で動いて別の環境で AccessDeniedException になるときは、実行ユーザーの違いを確かめます
/dev/fullは、書き込むと必ず「空きがない」と返す Linux の特別なファイルです。空き容量がないときのエラー文を確かめるのに使えます
直し方の例:フォルダの作成・上書き・文字コード
| やりたいこと | 書き方 |
|---|---|
| 途中のフォルダごと作ってから書き込む | Files.createDirectories(path.getParent()); のあと Files.writeString(path, text); |
| すでにあっても例外にしないフォルダ作成 | Files.createDirectories(dir)(createDirectory はすでにあると例外) |
| コピー先を上書きする | Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING) |
| なくても例外にしない削除 | Files.deleteIfExists(path) |
| Shift_JIS のファイルを読む | Files.readString(path, Charset.forName("Shift_JIS")) |
| 読む前に確かめる | Files.isRegularFile(path)、Files.isReadable(path) |
ストリームのエラー:Stream closed と EOFException
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
public class StreamErrors {
interface Action { void run() throws IOException; }
static void show(String label, Action action) {
try {
action.run();
System.out.println(label + "\n => 例外なし");
} catch (IOException e) {
System.out.println(label + "\n => " + e.getClass().getName() + ": " + e.getMessage());
}
}
public static void main(String[] args) throws IOException {
Path file = Files.createTempFile("demo", ".txt");
Files.writeString(file, "line1\nline2\n");
show("閉じた FileInputStream を read()", () -> {
InputStream in = new FileInputStream(file.toFile());
in.close();
in.read();
});
show("閉じた BufferedReader を readLine()", () -> {
BufferedReader reader = Files.newBufferedReader(file);
reader.close();
reader.readLine();
});
show("try-with-resources の外で readLine()", () -> {
BufferedReader keep;
try (BufferedReader reader = Files.newBufferedReader(file)) {
keep = reader;
}
keep.readLine();
});
show("閉じた FileOutputStream に write()", () -> {
FileOutputStream out = new FileOutputStream(file.toFile());
out.close();
out.write(1);
});
show("2バイトしかないデータを readInt()", () -> {
DataInputStream in = new DataInputStream(new ByteArrayInputStream(new byte[] {1, 2}));
in.readInt();
});
// データの終わりは例外ではなく、戻り値で知らされる
InputStream empty = new ByteArrayInputStream(new byte[0]);
System.out.println("データの終わりで read() => 戻り値 " + empty.read());
try (BufferedReader reader = Files.newBufferedReader(file)) {
reader.readLine();
reader.readLine();
System.out.println("最後の行の次で readLine() => 戻り値 " + reader.readLine());
}
Files.delete(file);
}
}
閉じた FileInputStream を read()
=> java.io.IOException: Stream Closed
閉じた BufferedReader を readLine()
=> java.io.IOException: Stream closed
try-with-resources の外で readLine()
=> java.io.IOException: Stream closed
閉じた FileOutputStream に write()
=> java.io.IOException: Stream Closed
2バイトしかないデータを readInt()
=> java.io.EOFException: null
データの終わりで read() => 戻り値 -1
最後の行の次で readLine() => 戻り値 null
- Stream closed は「閉じた後に使った」 という意味です。
FileInputStreamとFileOutputStreamではStream Closed(C が大文字)、BufferedReaderではStream closedと表示されます - よくあるのは、try-with-resources のブロックを出た後で、中で作ったストリームやリーダーを使う形です。ブロックを出た時点で自動的に閉じられています。読み書きはブロックの中で済ませ、外には読み取った結果(文字列やリスト)だけを持ち出します
- EOFException は、
DataInputStream.readInt()のように「決まったバイト数を読む」メソッドが、足りないまま終わりに達したときに出ます。メッセージは null です - ふつうの
read()やreadLine()は、終わりに達しても例外を出さず、-1 や null を返します。ループの終了条件にはこの戻り値を使います
ネットワークのエラーを再現する
確認用に、同じプログラムの中で2つのサーバーを立てています。ポート 9040 は「接続は受けるが何も返さない」、9041 は「接続を受けたらすぐ強制切断する」、9042 は誰も待ち受けていないポートです。
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class NetErrors {
interface Action { void run() throws Exception; }
static void show(String label, Action action) {
try {
action.run();
System.out.println(label + "\n => 例外なし");
} catch (IOException e) {
System.out.println(label + "\n => " + e.getClass().getName() + ": " + e.getMessage());
} catch (Exception e) {
System.out.println(label + "\n => (IOException 以外) " + e);
}
}
public static void main(String[] args) throws Exception {
// 確認用のサーバー1:接続は受けるが、何も返さない(ポート 9040)
ServerSocket silent = new ServerSocket(9040);
// 確認用のサーバー2:接続を受けたらすぐに強制切断する(ポート 9041)
ServerSocket reset = new ServerSocket(9041);
Thread t = new Thread(() -> {
try {
while (true) {
Socket s = reset.accept();
s.setSoLinger(true, 0); // 閉じるときに RST を送る
s.close();
}
} catch (IOException ignored) {
// サーバーを閉じたら終了
}
});
t.setDaemon(true);
t.start();
show("存在しないホスト名", () -> InetAddress.getByName("no-such-host.invalid"));
show("誰も待ち受けていないポートに接続", () -> new Socket("127.0.0.1", 9042).close());
show("応答が来ない相手を1秒で打ち切る(読み取り)", () -> {
try (Socket s = new Socket("127.0.0.1", 9040)) {
s.setSoTimeout(1000);
s.getInputStream().read();
}
});
show("相手に強制切断された後で read()", () -> {
try (Socket s = new Socket("127.0.0.1", 9041)) {
Thread.sleep(200);
InputStream in = s.getInputStream();
in.read();
}
});
show("閉じた Socket を使う", () -> {
Socket s = new Socket("127.0.0.1", 9040);
s.close();
s.getInputStream();
});
show("同じポートでもう1つ待ち受ける", () -> new ServerSocket(9040).close());
show("HttpClient:応答が来ない相手を1秒で打ち切る", () -> {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder(URI.create("http://127.0.0.1:9040/"))
.timeout(Duration.ofSeconds(1)).build();
client.send(request, HttpResponse.BodyHandlers.ofString());
});
silent.close();
reset.close();
}
}
存在しないホスト名
=> java.net.UnknownHostException: no-such-host.invalid: Name or service not known
誰も待ち受けていないポートに接続
=> java.net.ConnectException: Connection refused
応答が来ない相手を1秒で打ち切る(読み取り)
=> java.net.SocketTimeoutException: Read timed out
相手に強制切断された後で read()
=> java.net.SocketException: Connection reset
閉じた Socket を使う
=> java.net.SocketException: Socket is closed
同じポートでもう1つ待ち受ける
=> java.net.BindException: Address already in use
HttpClient:応答が来ない相手を1秒で打ち切る
=> java.net.http.HttpTimeoutException: request timed out
早見表のとおり、どの段階で止まったかがクラス名とメッセージに表れます。名前を引く段階なら UnknownHostException、接続の段階なら Connection refused、接続後に応答を待つ段階なら Read timed out、通信の途中で切れたら Connection reset です。Socket is closed は自分で閉じたソケットを使ったときに出ています。
待つ時間の上限を設定していない読み取りは、応答が来るまで待ち続けます。 Socket なら setSoTimeout(ミリ秒)、HttpClient なら HttpRequest.newBuilder(...).timeout(Duration.ofSeconds(秒)) で上限を決めておくと、上のように例外として知らせてくれます。
UncheckedIOExceptionの正体と扱い方
UncheckedIOException は、IOException を包んで運ぶための非検査例外です。API ドキュメントには「IOException を非検査例外でラップする」とあり、Java 8 で追加されました。コンストラクタは必ず IOException を受け取り、getCause() の戻り値の型も IOException です。
なぜ必要かというと、Stream の forEach や map に渡すラムダは、検査例外を投げられないからです。Files.lines は、ファイルを開くところまでは IOException を投げますが、Stream として読み進めている途中の入出力エラーは UncheckedIOException に包んで知らせます。Files.list や Files.walk も同じ仕組みです。
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
public class Unchecked {
public static void main(String[] args) throws IOException {
Path file = Path.of("work/sjis2.txt");
Files.createDirectories(file.getParent());
Files.write(file, "1行目\nこんにちは\n".getBytes("Shift_JIS"));
// Files.lines() を開くところまでは IOException(検査例外)
try (Stream<String> lines = Files.lines(file)) {
// 読み進めている途中の入出力エラーは UncheckedIOException で届く
lines.forEach(System.out::println);
} catch (UncheckedIOException e) {
System.out.println("受け取った例外: " + e.getClass().getName());
System.out.println("getMessage : " + e.getMessage());
IOException cause = e.getCause(); // 戻り値の型は IOException
System.out.println("getCause : " + cause.getClass().getName() + ": " + cause.getMessage());
}
}
}
受け取った例外: java.io.UncheckedIOException
getMessage : java.nio.charset.MalformedInputException: Input length = 1
getCause : java.nio.charset.MalformedInputException: Input length = 1
1行目は Shift_JIS のバイト列を UTF-8 として読めないため、何も表示されないまま例外になっています。catch (IOException e) だけでは、この例外は受け取れません(親子関係がないため)。Files.lines を使うときは、IOException と UncheckedIOException の両方を考えておきます。
ラムダの中で IOException を包み、外で取り出す
ラムダの中で IOException を投げるメソッドをそのまま呼ぶと、コンパイルエラーになります。
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class LambdaError {
public static void main(String[] args) {
List<Path> paths = List.of(Path.of("work/x.txt"));
List<String> texts = paths.stream()
.map(p -> Files.readString(p))
.toList();
System.out.println(texts);
}
}
LambdaError.java:9: error: unreported exception IOException; must be caught or declared to be thrown
.map(p -> Files.readString(p))
^
1 error
main に throws IOException を足しても、このエラーは消えません。ラムダは Function の apply メソッドの中身として扱われ、apply には throws がないためです。そこで、ラムダの中で UncheckedIOException に包み、Stream の外で getCause() を投げ直して IOException に戻します。
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.List;
import java.util.function.Function;
public class LambdaWrap {
/** IOException を投げる処理を表すインターフェース。 */
@FunctionalInterface
interface IOFunction<T, R> {
R apply(T t) throws IOException;
}
/** IOException を UncheckedIOException に包んで、Function として渡せるようにする。 */
static <T, R> Function<T, R> unchecked(IOFunction<T, R> f) {
return t -> {
try {
return f.apply(t);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
};
}
/** 外側では IOException に戻して投げ直す。 */
static List<String> readAll(List<Path> paths) throws IOException {
try {
return paths.stream()
.map(unchecked(Files::readString))
.toList();
} catch (UncheckedIOException e) {
throw e.getCause();
}
}
public static void main(String[] args) throws IOException {
Files.createDirectories(Path.of("work"));
Files.writeString(Path.of("work/x.txt"), "X");
Files.writeString(Path.of("work/y.txt"), "Y");
System.out.println(readAll(List.of(Path.of("work/x.txt"), Path.of("work/y.txt"))));
try {
readAll(List.of(Path.of("work/x.txt"), Path.of("work/zzz.txt")));
} catch (NoSuchFileException e) {
System.out.println("ファイルが見つかりません: " + e.getFile());
}
}
}
[X, Y]
ファイルが見つかりません: work/zzz.txt
unchecked(...)は一度書けば使い回せます。.map(unchecked(Files::readString))のように、メソッド参照をそのまま包めますgetCause()の戻り値が IOException 型なので、キャストなしでthrow e.getCause();と書けます- 外に出た後は、ふつうの IOException(ここでは NoSuchFileException)として、呼び出し元が種類ごとに受け取れます
catchを書く順番:子クラスを先に
catch は上から順に当てはめられます。親クラスの IOException を先に書くと、後ろの子クラスには届かなくなるため、コンパイルエラーになります。
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class CatchOrder {
public static void main(String[] args) {
try (FileReader reader = new FileReader("work/none.txt")) {
System.out.println(reader.read());
} catch (IOException e) {
System.out.println("入出力エラー: " + e.getMessage());
} catch (FileNotFoundException e) {
System.out.println("ファイルが見つかりません: " + e.getMessage());
}
}
}
CatchOrder.java:11: error: exception FileNotFoundException has already been caught
} catch (FileNotFoundException e) {
^
1 error
「FileNotFoundException はすでに捕捉されている」という意味です。子クラスを先に、親クラスを後に並べ替えると解消します。| で並べるマルチキャッチでも、親子関係にあるクラスは並べられません。
MultiCatch.java:9: error: Alternatives in a multi-catch statement cannot be related by subclassing
} catch (FileNotFoundException | IOException e) {
^
Alternative FileNotFoundException is a subclass of alternative IOException
1 error
この場合は catch (IOException e) だけで両方を受け取れます。
コピペ用:種類ごとに分けて受け取る
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.MalformedInputException;
import java.nio.file.AccessDeniedException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.stream.Stream;
public class CatchOrderFixed {
static void printAll(String name) {
try (Stream<String> lines = Files.lines(Path.of(name))) {
lines.forEach(System.out::println);
} catch (NoSuchFileException e) { // 子クラスを先に
System.out.println("ファイルが見つかりません: " + e.getFile());
} catch (AccessDeniedException e) {
System.out.println("読み取る権限がありません: " + e.getFile());
} catch (IOException e) { // 親クラスは後に
System.out.println("入出力エラー: " + e);
} catch (UncheckedIOException e) { // 読み進めている途中のエラー
if (e.getCause() instanceof MalformedInputException) {
System.out.println("文字コードが UTF-8 ではないようです: " + name);
} else {
System.out.println("読み取り中の入出力エラー: " + e.getCause());
}
}
}
public static void main(String[] args) throws IOException {
Files.createDirectories(Path.of("work"));
Files.writeString(Path.of("work/ok.txt"), "1行目\n2行目\n");
Files.write(Path.of("work/sjis3.txt"), "こんにちは\n".getBytes("Shift_JIS"));
printAll("work/ok.txt");
printAll("work/none.txt");
printAll("work");
printAll("work/sjis3.txt");
}
}
1行目
2行目
ファイルが見つかりません: work/none.txt
読み取り中の入出力エラー: java.io.IOException: Is a directory
文字コードが UTF-8 ではないようです: work/sjis3.txt
3つ目の printAll("work") では、フォルダを Files.lines に渡した結果、開く段階ではなく読み進める段階で Is a directory が UncheckedIOException として届いています。UncheckedIOException の catch があるおかげで受け取れました。
catch を並べるときの目安です。
| 方針 | 理由 |
|---|---|
| 子クラスを先、IOException を後 | 逆にするとコンパイルエラー |
| 対応を変えたい種類だけ個別に catch する | 「ない → 新しく作る」「権限 → 案内を出す」のように処理が違うものだけ分ける |
残りは catch (IOException e) でまとめる |
子クラスは多く、すべてを列挙する必要はない |
FileSystemException の子は e.getFile() で対象のパスを取れる |
メッセージを自分で組み立てるときに使う |
原因を探す手順
- 例外のクラス名を見る。
IOExceptionとだけ書かれていたらメッセージを、子クラスならクラス名を早見表で引く - メッセージのパス・ホスト名を見る。相対パスなら、実行時の作業フォルダ(
System.getProperty("user.dir"))からの位置になる Caused by:があれば一番下までたどる。UncheckedIOException や、ライブラリが包み直した例外の中に元の IOException がある- プログラムの外を確かめる。ファイルの有無と権限(
ls -l)、空き容量(df -h)、相手のサービスの起動とポート
よくある質問
Q. IOException と FileNotFoundException は、どちらを catch すればよいですか? A. 「ファイルがないときは既定値を使う」のように対応を変えたいときだけ FileNotFoundException(Files を使うなら NoSuchFileException)を先に catch し、そのあとに IOException を書きます。対応が同じなら IOException だけで十分です。
Q. throws IOException と try-catch は、どう使い分けますか?
A. そのメソッドの中で意味のある対応(別の値を使う、やり直す、利用者に案内する)ができるなら try-catch、できないなら throws IOException で呼び出し元に任せます。catch して何もしない書き方は、原因が見えなくなるので避けます。
まとめ
- IOException は入出力の例外の親クラス。原因は子クラスの名前とメッセージに出る
- Files のメソッドは原因ごとに別のクラス(NoSuchFileException、AccessDeniedException、FileAlreadyExistsException、DirectoryNotEmptyException)。java.io のクラスは FileNotFoundException のかっこの中を読む
- Stream closed は閉じた後の利用。読み書きは try-with-resources の中で済ませる
- ネットワークは段階で見分ける。名前が引けない(UnknownHostException)、接続できない(Connection refused)、応答が来ない(Read timed out)
- UncheckedIOException は IOException を包んだ非検査例外。
catch (IOException e)では受け取れず、getCause()で中身を取り出す - ラムダの中では UncheckedIOException に包み、外で
throw e.getCause();で IOException に戻す - catch は子クラスを先に、IOException を後に。逆はコンパイルエラーになる
参考・出典
- Java SE 21 API ドキュメント「IOException」 https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/IOException.html
- Java SE 21 API ドキュメント「UncheckedIOException」 https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/UncheckedIOException.html
- Java SE 21 API ドキュメント「Files」 https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/file/Files.html
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java IOExceptionの原因と対処|種類一覧とエラー文別の直し方」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。