File I/O exceptions in Java
IOException, NoSuchFileException, UncheckedIOException.
map(f -> Files.readString(...)). The compiler refuses. The method is fine, the lambda is fine… so what's the problem?Expect failure
Disks fill up, files vanish, permissions change. That's why most I/O methods throw **IOException, a checked exception: the compiler forces you to catch it or declare throws**.
Specific subclasses
NIO.2 reports precise causes: **NoSuchFileException (path doesn't exist), AccessDeniedException (no permission), FileAlreadyExistsException** (e.g. Files.createFile on an existing file). Old java.io classes like FileInputStream throw **FileNotFoundException** instead.
The missing file
Files.readString(Path.of("missing.txt")) runs and the file doesn't exist. What happens?
It returns an empty StringIt returns nullIt throws NoSuchFileException
Show the answer
It throws **NoSuchFileException, a subclass of IOException. A missing file is an error the caller must handle**, not a valid empty value.
Lambdas can't throw checked exceptions
Function.apply declares no checked exceptions, so a lambda calling Files.readString inside map doesn't compile. The standard fix: catch the IOException and rethrow it wrapped in **UncheckedIOException**. Files.lines does the same for read errors that happen mid-stream.
files.stream()
.map(f -> Files.readString(Path.of(f)))
.toList(); // compile errorFixing the lambda
.map(f -> {
try { return Files.readString(f); }
catch (Exception e) { return null; }
})Silently turns failures into nulls that explode later.
.map(f -> {
try { return Files.readString(f); }
catch (IOException e) {
throw new UncheckedIOException(e);
}
})Unchecked, so the lambda compiles, and getCause() still holds the original IOException.
The empty catch
catch (IOException e) { } makes failures invisible: the program carries on with missing data and nobody knows why. At minimum log it — better, rethrow or react to the specific subclass (create the missing file, alert on access denied).
try {
config = Files.readString(p);
} catch (IOException e) {
// swallowed: nobody will ever know
}Errors that tell the truth
Good services react to specific I/O failures: create a missing cache directory, return 404 for a missing upload, page someone on AccessDenied. Swallowed IOExceptions are among the hardest production bugs to diagnose, because the logs show nothing.
Key takeaways
- IOException is checked: catch it or declare throws
- NIO.2 throws NoSuchFileException; old java.io throws FileNotFoundException
- Files.lines wraps read errors in UncheckedIOException
- Never swallow I/O errors with an empty catch
UncheckedIOException arrived in Java 8 together with streams — BufferedReader.lines() and Files.lines() use it to report read errors from inside a stream.
Practice questions
Files.readString(Path.of("missing.txt")) is called and the file doesn't exist. What happens?
- It returns an empty String
- It throws NoSuchFileException
- It throws FileNotFoundException
- It returns null
Check your answer
It throws NoSuchFileException. NIO.2 methods report a missing file with NoSuchFileException, a subclass of IOException. FileNotFoundException comes from older java.io classes like FileInputStream.
What does this print?
List<String> texts = Stream.of("a.txt", "b.txt")
.map(f -> Files.readString(Path.of(f)))
.toList();
System.out.println(texts);- []
- Throws NoSuchFileException
- Compile error
- Throws UncheckedIOException
Check your answer
Compile error. readString throws the checked IOException, but Function.apply declares no checked exceptions, so the lambda doesn't compile.