- JAVA
Java FileNotFoundExceptionの原因と対処|相対パス早見表
FileNotFoundExceptionとは、指定したパスのファイルを開けなかったときにjava.ioのクラスが投げる例外です。 名前は「見つからない」ですが、フォルダを指している場合や権限が足りない場合にも同じ例外になります。見分ける手がかりは、メッセージ末尾の括弧書きです。
この記事では、括弧書きから原因と対処を引ける早見表、相対パスの基準になるカレントディレクトリ(user.dir)の確認コード、src/main/resources のファイルを getResourceAsStream で読む書き方、FileNotFoundException と NoSuchFileException がどのAPIから出るかの対応表をまとめます。Linux 側の出力はすべて OpenJDK 21.0.10(Ubuntu 24.04)で実際に動かした結果です。Windows 側のメッセージは今回の環境では動かせなかったため、「未実行」と明記したうえで一般に知られている文言を載せています。出力中のパスは /home/taro/work に置き換えて表示しています。
FileNotFoundExceptionの早見表(括弧書き別)
例外メッセージは「パス (OSからの理由)」の形をしています。まず括弧の中を読みます。
| 括弧書き(Linux・実行して確認) | Windows 日本語環境での文言(未実行) | 主な原因 | 対処 |
|---|---|---|---|
| No such file or directory | 指定されたファイルが見つかりません。 | ファイル名の違い、相対パスの基準(カレントディレクトリ)の違い、大文字小文字、末尾の空白 | toAbsolutePath で実際に探している場所を表示して確かめる |
| No such file or directory(書き込み時) | 指定されたパスが見つかりません。 | 出力先の親フォルダが無い | Files.createDirectories で親フォルダを先に作る |
| Is a directory | アクセスが拒否されました。 | ファイルではなくフォルダを開こうとした | パスの末尾にファイル名を足す。Files.isDirectory で確認 |
| Permission denied | アクセスが拒否されました。 | 読み取り・書き込み権限が無い、書き込み先フォルダに権限が無い | 権限を見直す、書き込める場所(ユーザーのホームや一時フォルダ)へ変える |
| (Linux では通常発生しない) | プロセスはファイルにアクセスできません。別のプロセスが使用中です。 | ほかのアプリがファイルを開いたまま | 開いているアプリを閉じる。自分のコードでは try-with-resources で確実に閉じる |
| 括弧書き無し(NoSuchFileException: パス) | 同じ形 | java.nio.file の API で、ファイルが無い | 上の「No such file or directory」と同じ手順 |
| 状況 | 最初に試すこと |
|---|---|
| IDEでは動くのにコマンドラインで止まる | user.dir を表示して、実行した場所を比べる |
| jarにすると止まる | src/main/resources のファイルを getResourceAsStream で読む形に変える |
| パスは正しいはずなのに止まる | 末尾の空白・改行、拡張子の二重化(data.txt.txt)、大文字小文字を確かめる |
| 書き込みで止まる | 親フォルダの有無と権限を確かめる |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java FileNotFoundExceptionの原因と対処|相対パス早見表」
まず確認:スタックトレースの読み方
次のような表示が出たら、1行目だけで原因の見当がつきます。
import java.io.FileReader;
public class St {
public static void main(String[] a) throws Exception {
new FileReader("data/input.txt");
}
}
実行結果(Java 21、Linux):
Exception in thread "main" java.io.FileNotFoundException: data/input.txt (No such file or directory)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:152)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:106)
at java.base/java.io.FileReader.<init>(FileReader.java:60)
at St.main(St.java:2)
| 読む場所 | この例での内容 | わかること |
|---|---|---|
| 例外クラス名 | java.io.FileNotFoundException | java.io 系の API で開けなかった |
| パス | data/input.txt | 相対パスで指定している(基準はカレントディレクトリ) |
| 括弧書き | No such file or directory | その場所に存在しない |
| 一番下の at 行 | St.main(St.java:2) | 自分のコードのどの行か |
パスが相対パスなら、次の章の「カレントディレクトリの確認」に進むのが近道です。
相対パスの基準を確認するコード(user.dir と toAbsolutePath)
相対パスは、Javaを起動したときのカレントディレクトリ(システムプロパティ user.dir)を基準に解決されます。 ソースファイルやclassファイルの場所ではありません。次のコードを貼って実行すると、実際に探している場所がわかります。
import java.nio.file.Files;
import java.nio.file.Path;
public class Where {
public static void main(String[] args) {
Path p = Path.of("data/ok.txt");
System.out.println("user.dir = " + System.getProperty("user.dir"));
System.out.println("relative path = " + p);
System.out.println("toAbsolutePath = " + p.toAbsolutePath());
System.out.println("exists = " + Files.exists(p));
}
}
フォルダ構成は次のとおりです。
/home/taro/work/
└── app/
├── Where.class
└── data/
└── ok.txt
実行結果(同じクラスを、場所を変えて2回実行):
$ cd /home/taro/work/app && java Where
user.dir = /home/taro/work/app
relative path = data/ok.txt
toAbsolutePath = /home/taro/work/app/data/ok.txt
exists = true
$ cd /home/taro/work && java -cp app Where
user.dir = /home/taro/work
relative path = data/ok.txt
toAbsolutePath = /home/taro/work/data/ok.txt
exists = false
同じプログラムでも、起動した場所が1階層違うだけで探す先が変わります。 「IDEでは動くのに、ターミナルやjarでは見つからない」という現象の多くはこれで説明できます。
実行方法ごとのカレントディレクトリ
| 実行方法 | カレントディレクトリ(既定) | 今回の確認 | 補足 |
|---|---|---|---|
| java コマンド(ターミナル) | コマンドを打った場所 | 実行して確認 | -cp で別フォルダのクラスを指定しても、基準は打った場所 |
| java -jar app.jar | コマンドを打った場所 | 実行して確認 | jarの置き場所ではない。/tmp から実行すると user.dir は /tmp だった |
| Gradle の run タスク(application プラグイン) | プロジェクトのフォルダ | 実行して確認(Gradle 8.14.3) | 別の場所から -p で指定して実行しても、プロジェクトのフォルダになった |
| Maven(exec プラグイン、テスト実行) | 一般に、モジュールのフォルダ(pom.xml のある場所) | 未実行 | この環境ではプラグインを取得できず動かせなかった |
| Eclipse | 一般に、プロジェクトのルートフォルダ | 未実行 | 実行構成の「引数」タブにある作業ディレクトリで変更できる |
| IntelliJ IDEA | 一般に、プロジェクトのルートフォルダ | 未実行 | 実行構成の Working directory で変更できる。複数モジュール構成ではモジュールのフォルダに合わせる設定もある |
| VS Code(Java拡張) | 一般に、開いているワークスペースのフォルダ | 未実行 | launch.json の cwd で変更できる |
IDE の行は今回の環境で動かせていないため、手元では上の Where クラスを実行して user.dir を直接確かめるのが確実です。IDE の種類を問わず、1回の実行で答えが出ます。
相対パスで止まったときの直し方
直す前(プロジェクト内の src フォルダからの位置を想定して書いている):
// カレントディレクトリが想定と違うと No such file or directory になる
var reader = new FileReader("data/ok.txt");
直した後は、ファイルの性質に合わせて3通りから選びます。
| ファイルの性質 | 選ぶ方法 | 書き方の例 |
|---|---|---|
| アプリに同梱する固定のファイル(設定の既定値、テンプレート、初期データ) | クラスパスリソースとして読む | Main.class.getResourceAsStream("/app.properties") |
| 利用者が指定する入力ファイル | 引数で受け取り、絶対パスに直して表示する | Path.of(args[0]).toAbsolutePath() |
| 実行環境ごとに置き場所が違うファイル | 環境変数やシステムプロパティで基準フォルダを渡す | Path.of(System.getenv("APP_HOME"), "data", "ok.txt") |
| jarの隣に置く設定ファイル | jarの場所から組み立てる | 後述の「jarの隣のファイルを読む」 |
なお、起動時に -Duser.dir=... を付けて基準を変える方法は、クラスパスの解決にも影響して動作が読みにくくなります(今回、-Duser.dir と相対の -cp を併用したところクラスが見つからなくなりました)。基準フォルダは自分の変数で持ち、Path の resolve で組み立てるほうが見通しがよくなります。
FileNotFoundExceptionとNoSuchFileExceptionの発生API対応表
「ファイルが無い」ときの例外は、使うAPIの系統で変わります。 java.io 系は FileNotFoundException、java.nio.file 系は NoSuchFileException です。どちらも IOException のサブクラスですが、互いに親子関係はないため、片方だけを catch していると素通りします。
次の検証コードで、同じパスを13種類のAPIに渡して例外を記録しました。
import java.io.*;
import java.nio.file.*;
import java.util.*;
public class Probe {
interface Op { void run(String p) throws Exception; }
public static void main(String[] a) {
Map<String, Op> ops = new LinkedHashMap<>();
ops.put("new FileInputStream(p)", p -> { try (var s = new FileInputStream(p)) {} });
ops.put("new FileReader(p)", p -> { try (var s = new FileReader(p)) {} });
ops.put("new Scanner(new File(p))", p -> { try (var s = new Scanner(new File(p))) {} });
ops.put("new RandomAccessFile(p,\"r\")", p -> { try (var s = new RandomAccessFile(p, "r")) {} });
ops.put("Files.readString(Path.of(p))", p -> Files.readString(Path.of(p)));
ops.put("Files.readAllLines(Path.of(p))", p -> Files.readAllLines(Path.of(p)));
ops.put("Files.newBufferedReader(Path.of(p))", p -> { try (var s = Files.newBufferedReader(Path.of(p))) {} });
ops.put("Files.newInputStream(Path.of(p))", p -> { try (var s = Files.newInputStream(Path.of(p))) { s.read(); } });
ops.put("Files.lines(Path.of(p))", p -> { try (var s = Files.lines(Path.of(p))) { s.count(); } });
ops.put("new Scanner(Path.of(p))", p -> { try (var s = new Scanner(Path.of(p))) {} });
for (String p : a) {
System.out.println("== " + p);
for (var e : ops.entrySet()) {
try { e.getValue().run(p); System.out.println(e.getKey() + " -> OK"); }
catch (Exception ex) {
System.out.println(e.getKey() + " -> " + ex.getClass().getName() + ": " + ex.getMessage());
}
}
}
}
}
(書き込み用の new FileOutputStream(p, true)、new PrintWriter(p)、Files.writeString(Path.of(p), "", StandardOpenOption.APPEND) も同じ形で追加して測っています。)
存在しないファイル data/none.txt を渡したときの実行結果(Java 21、Linux):
== data/none.txt
new FileInputStream(p) -> java.io.FileNotFoundException: data/none.txt (No such file or directory)
new FileReader(p) -> java.io.FileNotFoundException: data/none.txt (No such file or directory)
new Scanner(new File(p)) -> java.io.FileNotFoundException: data/none.txt (No such file or directory)
new RandomAccessFile(p,"r") -> java.io.FileNotFoundException: data/none.txt (No such file or directory)
Files.readString(Path.of(p)) -> java.nio.file.NoSuchFileException: data/none.txt
Files.readAllLines(Path.of(p)) -> java.nio.file.NoSuchFileException: data/none.txt
Files.newBufferedReader(Path.of(p)) -> java.nio.file.NoSuchFileException: data/none.txt
Files.newInputStream(Path.of(p)) -> java.nio.file.NoSuchFileException: data/none.txt
Files.lines(Path.of(p)) -> java.nio.file.NoSuchFileException: data/none.txt
new Scanner(Path.of(p)) -> java.nio.file.NoSuchFileException: data/none.txt
状況×APIの対応表(Linuxで実行して確認)
| 状況 | java.io 系(FileInputStream / FileReader / Scanner(File) / RandomAccessFile / FileOutputStream / PrintWriter) | java.nio.file 系(Files.readString / readAllLines / newInputStream / lines / Scanner(Path)) |
|---|---|---|
| ファイルが無い(読み込み) | FileNotFoundException: パス (No such file or directory) | NoSuchFileException: パス |
| 親フォルダが無い(書き込み) | FileNotFoundException: パス (No such file or directory) | NoSuchFileException: パス |
| フォルダを指定した | FileNotFoundException: パス (Is a directory) | IOException: Is a directory(Files.lines は UncheckedIOException で包まれる) |
| 読み取り権限が無い | FileNotFoundException: パス (Permission denied) | AccessDeniedException: パス |
| 書き込み権限が無いファイルへ書く | FileNotFoundException: パス (Permission denied) | AccessDeniedException: パス |
| 書き込めないフォルダに新規作成 | FileNotFoundException: パス (Permission denied) | APPEND のみ指定では NoSuchFileException(作成を指示していないため) |
| 末尾に空白が付いたパス("data/ok.txt ") | FileNotFoundException: data/ok.txt (No such file or directory) | NoSuchFileException: data/ok.txt |
| 大文字小文字が違う(data/OK.txt) | FileNotFoundException(Linux は区別する) | NoSuchFileException |
| 空文字 "" | FileNotFoundException: (No such file or directory) | IOException: Is a directory(空のパスはカレントディレクトリを指す) |
フォルダを指定したときの補足として、Files.newBufferedReader と new Scanner(Path) は開く時点では例外にならず、読み始めた時点で IOException になります。権限の行は、root では権限チェックを通過してしまうため、一般ユーザー(nobody)に切り替えて確認しました。
Windows での対応(未実行)は、一般に次のように知られています。
| 状況 | java.io 系のメッセージ(Windows 日本語環境・未実行) |
|---|---|
| ファイルが無い | パス (指定されたファイルが見つかりません。) |
| 途中のフォルダが無い | パス (指定されたパスが見つかりません。) |
| フォルダを指定した/権限が無い | パス (アクセスが拒否されました。) |
| ほかのアプリが使用中 | パス (プロセスはファイルにアクセスできません。別のプロセスが使用中です。) |
| パスに使えない文字がある | パス (ファイル名、ディレクトリ名、またはボリューム ラベルの構文が間違っています。) |
Windows では「フォルダを開こうとした」場合も「アクセスが拒否されました」と表示される点が、Linux の Is a directory との違いです。 権限を疑う前に、パスがフォルダで終わっていないかを確かめると早く解決します。java.nio.file 系の例外クラス(NoSuchFileException、AccessDeniedException)は OS によらず同じです。
両方をまとめて受け取る書き方
java.nio.file 系で書くと、例外クラスで原因を分けられます。
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.AccessDeniedException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
public class SafeRead {
public static void main(String[] args) {
Path path = Path.of(args.length > 0 ? args[0] : "data/ok.txt");
try {
String text = Files.readString(path, StandardCharsets.UTF_8);
System.out.println("OK: " + text.strip());
} catch (NoSuchFileException e) {
System.out.println("見つかりません: " + path.toAbsolutePath());
System.out.println("カレントディレクトリ: " + System.getProperty("user.dir"));
} catch (AccessDeniedException e) {
System.out.println("権限がありません: " + path.toAbsolutePath());
} catch (IOException e) {
System.out.println("読み込めません: " + e);
}
}
}
実行結果(引数を変えて4回。最後の1回だけ一般ユーザーで実行):
$ java SafeRead
OK: hello
$ java SafeRead data/none.txt
見つかりません: /home/taro/work/app/data/none.txt
カレントディレクトリ: /home/taro/work/app
$ java SafeRead data
読み込めません: java.io.IOException: Is a directory
$ java SafeRead data/locked.txt
権限がありません: /home/taro/work/app/data/locked.txt
エラーメッセージに絶対パスとカレントディレクトリを出しておくと、次に同じことが起きたときの調査が数秒で済みます。 java.io 系と混在している場合は、共通の親である IOException で受け、e.getMessage() の括弧書きを表示すれば十分です。
| 方針 | 向いている場面 |
|---|---|
| NoSuchFileException と AccessDeniedException を分けて catch | 利用者に「無い」「権限が無い」を分けて伝えたい |
| IOException でまとめて catch | 原因を問わず同じ処理(ログを出して終了)でよい |
| 事前に Files.exists で確認してから開く | 無いときに別の処理(既定値を使う)へ分岐したい。確認と開く処理の間にファイルが消えることもあるので、catch も併用する |
src/main/resources のファイルを読む(getResourceAsStream)
src/main/resources に置いたファイルは、ビルド時にクラスと同じ場所(クラスパス)へコピーされます。 そのため「ファイルのパス」ではなく「クラスパス上の名前」で読みます。この方法ならカレントディレクトリの影響を受けず、jar にまとめても同じコードで動きます。
proj/
└── src/main/
├── java/app/Main.java
└── resources/
├── app.properties (中身: greeting=hello)
└── config/items.csv (中身: id,name と 1,pen の2行)
package app;
import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
public class Main {
public static void main(String[] args) throws Exception {
System.out.println("user.dir = " + System.getProperty("user.dir"));
// 1) Class#getResourceAsStream: 先頭の / はクラスパスの根から
try (InputStream in = Main.class.getResourceAsStream("/app.properties")) {
Properties p = new Properties();
p.load(in);
System.out.println("[1] /app.properties -> " + p.getProperty("greeting"));
}
// 2) サブフォルダのファイルを1行ずつ読む
try (InputStream in = Main.class.getResourceAsStream("/config/items.csv");
BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
System.out.println("[2] /config/items.csv -> " + r.readLine() + " / " + r.readLine());
}
// 3) 先頭の / が無いと、クラスのパッケージ(app/)からの相対になる
System.out.println("[3] Class \"app.properties\" -> " + Main.class.getResourceAsStream("app.properties"));
// 4) ClassLoader 経由は常に根から。先頭に / を付けない
ClassLoader cl = Main.class.getClassLoader();
System.out.println("[4] Loader \"app.properties\" -> " + (cl.getResourceAsStream("app.properties") != null));
System.out.println("[5] Loader \"/app.properties\" -> " + cl.getResourceAsStream("/app.properties"));
// 6) URL を見ると、どこから読んでいるかがわかる
URL url = Main.class.getResource("/app.properties");
System.out.println("[6] URL = " + url);
// 7) FileInputStream で開けるかどうか
try (InputStream in = new FileInputStream("src/main/resources/app.properties")) {
System.out.println("[7] FileInputStream(src/main/resources/...) -> OK");
} catch (FileNotFoundException e) {
System.out.println("[7] " + e);
}
try {
File f = new File(url.toURI());
System.out.println("[8] new File(url.toURI()) -> " + f.exists());
} catch (IllegalArgumentException e) {
System.out.println("[8] " + e);
}
try (InputStream in = new FileInputStream(url.getPath())) {
System.out.println("[9] FileInputStream(url.getPath()) -> OK");
} catch (FileNotFoundException e) {
System.out.println("[9] " + e);
}
}
}
ビルドと実行のコマンド(外部ツール無しで再現できる形):
mkdir -p out/classes
javac -encoding UTF-8 -d out/classes src/main/java/app/Main.java
cp -r src/main/resources/. out/classes/
java -cp out/classes app.Main # (A) クラスのフォルダから実行
jar --create --file out/app.jar --main-class app.Main -C out/classes .
java -jar out/app.jar # (B) jar を、プロジェクトのフォルダで実行
cd /tmp && java -jar /home/taro/work/proj/out/app.jar # (C) jar を、別の場所で実行
(A) クラスのフォルダから実行した結果:
user.dir = /home/taro/work/proj
[1] /app.properties -> hello
[2] /config/items.csv -> id,name / 1,pen
[3] Class "app.properties" -> null
[4] Loader "app.properties" -> true
[5] Loader "/app.properties" -> null
[6] URL = file:/home/taro/work/proj/out/classes/app.properties
[7] FileInputStream(src/main/resources/...) -> OK
[8] new File(url.toURI()) -> true
[9] FileInputStream(url.getPath()) -> OK
(C) jar を別の場所(/tmp)で実行した結果:
user.dir = /tmp
[1] /app.properties -> hello
[2] /config/items.csv -> id,name / 1,pen
[3] Class "app.properties" -> null
[4] Loader "app.properties" -> true
[5] Loader "/app.properties" -> null
[6] URL = jar:file:/home/taro/work/proj/out/app.jar!/app.properties
[7] java.io.FileNotFoundException: src/main/resources/app.properties (No such file or directory)
[8] java.lang.IllegalArgumentException: URI is not hierarchical
[9] java.io.FileNotFoundException: file:/home/taro/work/proj/out/app.jar!/app.properties (No such file or directory)
(B) の jar をプロジェクトのフォルダで実行した場合は、[7] だけが OK になり、[8][9] は (C) と同じ結果でした。Gradle 8.14.3 の run タスクでも [1]〜[5] は同じ結果で、[6] の URL は build/resources/main/app.properties を指していました。
結果の読み方
| 番号 | 書き方 | クラスのフォルダ | jar | 判断 |
|---|---|---|---|---|
| [1][2] | Main.class.getResourceAsStream("/名前") | 読める | 読める | この形を使う |
| [3] | Main.class.getResourceAsStream("名前")(先頭の / なし) | null | null | クラスと同じパッケージ(app/)の中を探す。同じパッケージに置くなら使える |
| [4] | getClassLoader().getResourceAsStream("名前") | 読める | 読める | 先頭に / を付けない形で使う |
| [5] | getClassLoader().getResourceAsStream("/名前") | null | null | ClassLoader 側は / を付けると見つからない |
| [7] | new FileInputStream("src/main/resources/...") | 読める | 実行場所しだい | 開発中だけ動く。配布物には src フォルダが無い |
| [8] | new File(url.toURI()) | 読める | IllegalArgumentException: URI is not hierarchical | jar の中身は File として扱えない |
| [9] | new FileInputStream(url.getPath()) | 読める | FileNotFoundException | 同上。パスに「app.jar!/」が入る |
覚えることは2つです。Class 経由なら先頭に / を付ける、ClassLoader 経由なら付けない。 そして、リソースは File ではなく InputStream として扱います。
getResourceAsStream が null を返すとき
getResourceAsStream は、見つからないときに例外ではなく null を返します。そのまま使うと、別の例外として表面化します。
import java.io.*;
import java.util.Properties;
public class Npe {
public static void main(String[] a) throws Exception {
InputStream in = Npe.class.getResourceAsStream("/nothing.properties");
System.out.println("in = " + in);
new Properties().load(in);
}
}
in = null
Exception in thread "main" java.lang.NullPointerException: inStream parameter is null
at java.base/java.util.Objects.requireNonNull(Objects.java:259)
at java.base/java.util.Properties.load(Properties.java:410)
at Npe.main(Npe.java:7)
null のときに自分でわかりやすい例外へ変えておくと、原因にすぐたどり着けます。
static InputStream open(String name) throws FileNotFoundException {
InputStream in = Main.class.getResourceAsStream(name);
if (in == null) {
throw new FileNotFoundException("クラスパスに見つかりません: " + name);
}
return in;
}
| null になる原因 | 確認方法 |
|---|---|
| 先頭の / の有無が逆 | 上の表の [3] と [5] を見直す |
| ビルド出力にコピーされていない | Gradle なら build/resources/main、Maven なら一般に target/classes(未実行)を開いて、ファイルがあるか見る |
| 置き場所が src/main/resources の外 | フォルダ名のつづり(resource と resources)を確かめる |
| jar に入っていない | jar --list --file app.jar で中身の一覧を表示する |
| 大文字小文字の違い | jar の中の名前は大文字小文字を区別する |
jarの隣のファイルを読む
利用者が書き換える設定ファイルは jar の中に入れず、jar の隣に置くことがあります。その場合はカレントディレクトリではなく、jar の場所から組み立てます。
package app;
import java.nio.file.Path;
public class Loc {
public static void main(String[] args) throws Exception {
Path jar = Path.of(Loc.class.getProtectionDomain().getCodeSource().getLocation().toURI());
Path conf = jar.getParent().resolve("config.txt");
System.out.println("user.dir = " + System.getProperty("user.dir"));
System.out.println("jar = " + jar);
System.out.println("conf = " + conf);
}
}
/tmp から実行した結果:
user.dir = /tmp
jar = /home/taro/work/proj/out/app.jar
conf = /home/taro/work/proj/out/config.txt
どこから起動しても、jar の隣の config.txt を指せています。
書き込みで出るFileNotFoundExceptionの対処
読み込みだけでなく、書き込みでも同じ例外が出ます。もっとも多いのは出力先の親フォルダが無いケースです。FileOutputStream や PrintWriter はファイルを新しく作りますが、途中のフォルダまでは作りません。
new FileOutputStream(p) -> java.io.FileNotFoundException: nodir/new.txt (No such file or directory)
new PrintWriter(p) -> java.io.FileNotFoundException: nodir/new.txt (No such file or directory)
Files.writeString(Path.of(p),..,APPEND) -> java.nio.file.NoSuchFileException: nodir/new.txt
親フォルダを先に作る完成形のコードです。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class Mk {
public static void main(String[] args) throws IOException {
Path out = Path.of("out/report/2026/result.txt");
Files.createDirectories(out.getParent()); // 親フォルダをまとめて作る(あっても例外にならない)
Files.writeString(out, "done\n");
System.out.println("wrote " + out.toAbsolutePath());
}
}
$ java Mk.java
wrote /home/taro/work/app/out/report/2026/result.txt
$ java Mk.java
wrote /home/taro/work/app/out/report/2026/result.txt
2回続けて実行しても同じ結果になります。Files.createDirectories は、フォルダがすでにあっても例外になりません。
| 書き込み時のメッセージ | 原因 | 対処 |
|---|---|---|
| (No such file or directory) | 親フォルダが無い | Files.createDirectories(path.getParent()) |
| (Is a directory) | 出力先にフォルダを指定した | ファイル名まで含めたパスにする |
| (Permission denied) | ファイルまたはフォルダに書き込み権限が無い | 書き込める場所に変える。System.getProperty("java.io.tmpdir") や user.home を基準にする |
原因を切り分けるチェックリスト
上から順に確かめると、ほとんどのケースで原因がわかります。
| 順番 | 確かめること | コード・コマンド |
|---|---|---|
| 1 | 括弧書きは何か | 例外の1行目を読む |
| 2 | 実際に探している絶対パス | System.out.println(Path.of(p).toAbsolutePath()) |
| 3 | カレントディレクトリ | System.getProperty("user.dir") |
| 4 | その場所に本当にあるか | ls -l 絶対パス(Windows は dir) |
| 5 | フォルダではないか | Files.isDirectory(path) |
| 6 | 読めるか | Files.isReadable(path) |
| 7 | 名前に余分な文字が無いか | System.out.println("[" + p + "]") で前後の空白や改行を見える形にする |
| 8 | 同梱ファイルなら | getResourceAsStream に切り替える |
7 は、設定ファイルや標準入力から読んだパスでよく起きます。今回の検証でも、末尾に空白が1つ付いただけで No such file or directory になりました。入力から受け取ったパスは strip() で前後の空白を落としてから使うと安定します。
flowchart TD
A[FileNotFoundException] --> B{括弧書き}
B -->|No such file or directory| C[toAbsolutePathで場所を確認]
C --> D{同梱ファイルか}
D -->|はい| E[getResourceAsStreamで読む]
D -->|いいえ| F[引数や設定で場所を渡す]
B -->|Is a directory| G[ファイル名まで指定する]
B -->|Permission denied| H[権限と置き場所を見直す]
B -->|書き込み時| I[createDirectoriesで親を作る]
よくある質問
Q. FileNotFoundException は catch すべきですか、それとも throws でよいですか? A. 検査例外なので、どちらかが必要です。利用者にわかりやすい案内を出せる層(画面やコマンドの入口)で catch し、絶対パスを添えて表示するのがおすすめです。途中の層では throws IOException で上に渡すと、コードがすっきりします。
Q. Windows でパスを "C:\data\a.txt" と書いたらコンパイルエラーになりました。 A. Java の文字列では \ がエスケープ文字なので、"C:\data\a.txt" と2つ重ねるか、"C:/data/a.txt" とスラッシュで書きます。Path.of("C:", "data", "a.txt") のように区切って渡す方法なら、OS の違いを意識せずに済みます(この項目は Windows での動作を今回確認していません)。
Q. File.exists() が true なのに開けないのはなぜですか? A. フォルダでも exists() は true になります。また、存在していても読み取り権限が無い場合があります。Files.isRegularFile と Files.isReadable を合わせて確認すると区別できます。
Q. NoSuchFileException を FileNotFoundException として catch できますか? A. できません。どちらも IOException のサブクラスですが、互いに継承関係がありません。両方を受けたいときは IOException で catch するか、2つを並べて catch します。
まとめ
- FileNotFoundException は括弧書きを読む。No such file or directory、Is a directory、Permission denied で原因が分かれる
- 相対パスの基準は user.dir(起動した場所)。toAbsolutePath で実際に探している場所を表示するのが最短の調査手順
- java、java -jar はコマンドを打った場所、Gradle の run はプロジェクトのフォルダが基準になる(実行して確認)。IDE は Where クラスで確かめる
- java.io 系は FileNotFoundException、java.nio.file 系は NoSuchFileException/AccessDeniedException。まとめて受けるなら IOException
- src/main/resources のファイルは getResourceAsStream("/名前") で読む。jar の中身は File として扱えない
- getResourceAsStream は見つからないと null を返す。null チェックを入れて、わかりやすい例外に変える
- 書き込み時は Files.createDirectories で親フォルダを先に作る
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java FileNotFoundExceptionの原因と対処|相対パス早見表」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。