Files.readString()
Read a whole small file into one String in a single call, choose the right charset, and know when the file is too big for it.
public static String readString(Path path, Charset cs) throws IOException- Added in Java Java 11
Parameters
| Parameter | Type | Description |
|---|---|---|
$path | Path | The file to read. |
$csoptional | Charset= StandardCharsets.UTF_8 | The charset used to decode the bytes. Leave it out to use readString(Path), which decodes as UTF-8. |
Return value
StringThe entire contents of the file decoded with the given charset (UTF-8 when the one-argument overload is used).
Errors
IOException if the file cannot be read (NoSuchFileException when it does not exist, AccessDeniedException without permission); MalformedInputException, a subclass of IOException, when the bytes are not valid in the charset; OutOfMemoryError when the file is extremely large, for example over 2 GB.
Files.readString(path) opens the file, reads every byte, decodes it and closes the file again. It arrived in Java 11 and is the shortest correct way to load a small text file: a JSON config, an SQL script, an email template, a test fixture.
Charset
The one-argument form always decodes as UTF-8, whatever the platform default is. Pass a Charset when the file comes from somewhere that does not write UTF-8, such as an old export from a Windows system. If the bytes are not valid for the charset, the method does not quietly insert replacement characters: it throws MalformedInputException. That is usually what you want in a service, because a half-decoded config is worse than a clear failure.
Size is the real limit
The whole file lands in the heap, and a String holds the decoded characters, so memory use is at least the file size. That is fine for a few kilobytes. For an uploaded CSV, a log, or anything a user controls, either check Files.size(path) first and refuse large files, or stream it with Files.lines or Files.newBufferedReader. A file over 2 GB cannot fit in one String at all and fails with OutOfMemoryError.
Mistakes people make
- Calling
Files.existsfirst and then reading. The file can disappear in between; catchNoSuchFileExceptioninstead. - Reading a file from inside the application JAR with
Path.of. Classpath resources are not files once packaged; usegetResourceAsStreamandreadAllBytes. - Relying on relative paths. They resolve against the working directory, which differs between your IDE, a container and a systemd unit.
String json = Files.readString(Path.of("config/app.json"));
String legacy = Files.readString(Path.of("import/old.txt"), StandardCharsets.ISO_8859_1);In a web service, read configuration once at startup and keep the result, rather than reading the file on every request.
Example
When to use it
- Load a JSON or YAML configuration file once at service startup and pass the text to a parser.
- Read an HTML or email template from disk and fill in placeholders before sending a notification.
- Load expected-response fixtures in integration tests and compare them with what an endpoint returned.
Frequently asked questions
How do I read a whole file into a String in Java?
Since Java 11, call Files.readString(Path.of("file.txt")). It reads the entire file as UTF-8 and closes it for you. On Java 8, the equivalent is new String(Files.readAllBytes(path), StandardCharsets.UTF_8), which silently replaces invalid bytes with U+FFFD instead of throwing.
Why does Files.readString throw MalformedInputException?
The file contains bytes that are not valid in the charset, usually a file saved as ISO-8859-1 or Windows-1252 being read as UTF-8. Pass the correct charset as the second argument, or convert the file to UTF-8.
Is Files.readString safe for large files?
No. It loads everything into memory and fails for files over about 2 GB. Use Files.lines or Files.newBufferedReader to process large files line by line.
More examples
Reading a file that is not UTF-8
The byte 0xE9 is not valid UTF-8 on its own, so the default overload throws instead of guessing. Decoding with the charset the file was really written in gives the four expected characters, the last one being U+00E9.
void main() throws IOException {
Path file = Files.createTempFile("legacy", ".txt");
try {
// "cafe" with an accented e, as an old ISO-8859-1 system writes it: one byte 0xE9.
Files.write(file, new byte[] {'c', 'a', 'f', (byte) 0xE9});
try {
Files.readString(file);
} catch (MalformedInputException e) {
IO.println("UTF-8 failed: " + e.getMessage());
}
String text = Files.readString(file, StandardCharsets.ISO_8859_1);
IO.println("Latin-1 length: " + text.length());
IO.println("last char code: " + (int) text.charAt(3));
} finally {
Files.deleteIfExists(file);
}
}
// Output:
// UTF-8 failed: Input length = 1
// Latin-1 length: 4
// last char code: 233Optional file with a fallback
Catching NoSuchFileException handles the missing case without a separate exists check that could race with another process. Any other IOException, such as a permissions problem, still propagates, which is what you want for a real fault.
String loadOrDefault(Path file, String fallback) throws IOException {
try {
return Files.readString(file);
} catch (NoSuchFileException e) {
return fallback;
}
}
void main() throws IOException {
Path dir = Files.createTempDirectory("cfg");
Path banner = dir.resolve("banner.txt");
try {
IO.println(loadOrDefault(banner, "Welcome"));
Files.writeString(banner, "Scheduled maintenance at 02:00 UTC");
IO.println(loadOrDefault(banner, "Welcome"));
} finally {
Files.deleteIfExists(banner);
Files.deleteIfExists(dir);
}
}
// Output:
// Welcome
// Scheduled maintenance at 02:00 UTCRefusing files that are too large
A size check before readString protects the heap when the file comes from a user or another system. Anything over the limit should be streamed with Files.lines or a BufferedReader instead.
static final long MAX_BYTES = 64 * 1024;
String readSmall(Path file) throws IOException {
long size = Files.size(file);
if (size > MAX_BYTES) {
throw new IOException(file.getFileName() + " is " + size + " bytes, limit is " + MAX_BYTES);
}
return Files.readString(file);
}
void main() throws IOException {
Path dir = Files.createTempDirectory("tpl");
Path small = dir.resolve("welcome.html");
Path big = dir.resolve("export.csv");
try {
Files.writeString(small, "<h1>Hello {{name}}</h1>");
Files.writeString(big, "id,total\n".repeat(10_000));
IO.println(readSmall(small).replace("{{name}}", "Ada"));
try {
readSmall(big);
} catch (IOException e) {
IO.println("rejected: " + e.getMessage());
}
} finally {
Files.deleteIfExists(small);
Files.deleteIfExists(big);
Files.deleteIfExists(dir);
}
}
// Output:
// <h1>Hello Ada</h1>
// rejected: export.csv is 90000 bytes, limit is 65536
Discussion