Java Read File to String: readString() and 7 More Ways

Learn to read a file to String in Java with Files.readString() (Java 11), Files.readAllBytes(), Files.readAllLines() and lines(), BufferedReader, Scanner and FileInputStream, plus how to read from the classpath, avoid charset and line separator mistakes and stream large files.

In Java, we read a file to String with Files.readString(path), which opens the file, decodes its bytes as UTF-8, closes the file and returns the whole text in one call. The method is in the Files class since Java 11. Older Java versions use Files.readAllBytes(), Files.readAllLines() or a BufferedReader, and all of them give us the same String.

We read a whole file into a String when the content is small and we need all of it at once. Typical files are a template, a JSON or SQL file, a config file or a test fixture. When the file is large or we only need some lines, we read it line by line instead, which we cover in section 9.

The following example reads the file recipe.txt, which has three lines, with the three shortest methods. Each line shows the result for the same file.

Path recipe = Path.of("recipe.txt");

String text = Files.readString(recipe);                          // "Pancakes\nMix flour, milk and one egg.\nFry both sides.\n"
String latin = Files.readString(recipe, StandardCharsets.ISO_8859_1);   // same text, decoded as ISO-8859-1

byte[] bytes = Files.readAllBytes(recipe);                       // 54 bytes
String fromBytes = new String(bytes, StandardCharsets.UTF_8);    // same text as readString()

List<String> lines = Files.readAllLines(recipe);                 // 3 lines, line breaks removed
String joined = String.join("\n", lines);                        // "Pancakes\nMix flour, milk and one egg.\nFry both sides."

Notice that readString() and readAllBytes() keep the last line break, whereas the joined list of lines does not. The rest of the article explains each method, adds BufferedReader, Scanner, FileInputStream and classpath files, and ends with the two mistakes we see most often (wrong charset, wrong line breaks) and a table that says which method to pick.

1. Java Read File to String with Files.readString()

The Files.readString() method reads all content from a file into a String. It decodes the bytes as UTF-8 unless we pass a second argument with another charset (the table that maps bytes to characters). The method opens and closes the file itself, so we have nothing to clean up, even when the read fails in the middle.

The following example reads recipe.txt once with the default charset and once with ISO-8859-1. The method throws a NoSuchFileException when the path does not exist, and we catch it so the program keeps running.

Path recipe = Path.of("recipe.txt");

String text = Files.readString(recipe);                                 // the whole file as one String
String latin = Files.readString(recipe, StandardCharsets.ISO_8859_1);   // the same file, decoded as ISO-8859-1

try {
  String missing = Files.readString(Path.of("missing.txt"));           // NoSuchFileException: missing.txt
} catch (NoSuchFileException e) {
  System.out.println("No such file: " + e.getMessage());
}

The readString() method either returns the whole text or throws an IOException. One case of that exception is a MalformedInputException, which means a byte in the file does not fit the charset. We look at that case in section 8. The method also throws an OutOfMemoryError) when the file is larger than about 2 GB, because a Java String cannot hold more characters than that.

In real code, we wrap the call in a small method that handles the exception in one place. The following method returns an empty string when the file cannot be read and logs the reason.

static String readOrEmpty(Path path) {
  try {
    return Files.readString(path);
  } catch (IOException e) {
    System.err.println("Cannot read " + path + ": " + e);
    return "";
  }
}

String safe = readOrEmpty(Path.of("recipe.txt"));   // 54 chars, or "" when the file is missing

1.1. Strict Decoding and the UTF-8 BOM

The one-argument Files.readString(path) is the same call as Files.readString(path, StandardCharsets.UTF_8). Both versions decode strictly, which means a byte sequence that is not valid in the charset throws a MalformedInputException instead of turning into a replacement character. The String constructor that we use with readAllBytes() in section 2 behaves differently, because it replaces each bad byte sequence with the character U+FFFD and never throws.

Path latin = Path.of("latin.txt");
Files.writeString(latin, "Cr\u00e8me", StandardCharsets.ISO_8859_1);

String lenient = new String(Files.readAllBytes(latin), StandardCharsets.UTF_8);   // "Cr\uFFFDme", no exception
boolean replaced = lenient.contains("\uFFFD");                                    // true
String strict = Files.readString(latin);                                          // MalformedInputException: Input length = 3

So readString() is the safer choice when wrong characters must not reach the database, because the bad file fails at the read and not later in a report.

Strict decoding does not cover the byte order mark (BOM), which is three bytes (EF BB BF) that some editors write at the start of a UTF-8 file. The readString() method keeps the BOM and returns it as the invisible character \uFEFF at index 0.

For example, a price import job reads a CSV file that a user saved from a spreadsheet app with the “CSV UTF-8” option. The first header looks like “id” in every editor, but startsWith(“id”) returns false, so the job cannot find the column. We remove the BOM before we parse the text.

Path csv = Path.of("prices.csv");   // bytes EF BB BF, then "id,price\n"

String text = Files.readString(csv);
int length = text.length();                                              // 10, one more than "id,price\n"
boolean hasBom = text.charAt(0) == '\uFEFF';                             // true
boolean header = text.startsWith("id");                                  // false
String clean = text.startsWith("\uFEFF") ? text.substring(1) : text;
boolean fixed = clean.startsWith("id");                                  // true

The other methods in this article keep the BOM too, including readAllLines() and BufferedReader, so the same startsWith(“\uFEFF”) check works after any of them. When we write a UTF-8 file with Files.writeString(), Java does not add a BOM.

2. Using Files.readAllBytes() (Java 7)

Before Java 11, the shortest way to read a file to String was Files.readAllBytes(). It reads the whole file into a byte[], and the String constructor decodes the bytes with the charset we pass. The method closes the file when it has read all bytes or when an error happens, like readString() does.

Always pass the charset to the String constructor. Without it, the constructor uses the default charset of the JVM. Since Java 18 that default is UTF-8 everywhere, but on Java 17 and older it depends on the operating system, so the same code could decode a file one way on Windows and another way on Linux.

Path recipe = Path.of("recipe.txt");

byte[] bytes = Files.readAllBytes(recipe);                       // 54 bytes
String text = new String(bytes, StandardCharsets.UTF_8);         // "Pancakes\nMix flour, milk and one egg.\nFry both sides.\n"
boolean same = text.equals(Files.readString(recipe));            // true

The readAllBytes() method has the same 2 GB limit as readString(), and the file is in memory twice for a short time, once as bytes and once as the String. For a small file that does not matter.

3. Using Files.readAllLines() and Files.lines() (Java 7 and 8)

Both methods give us the lines of the file without the line breaks. The Files.readAllLines() method reads the whole file and returns a List<String>. The Files.lines() method returns a Stream that reads the next line from the file only when the stream asks for it. To get one String back, we join the lines with a separator of our choice.

The following example joins the lines with “\n”. We use String.join() for the list and Collectors.joining() for the stream. The stream keeps the file open until the stream is closed, so we create it inside a try-with-resources block. On Java 7, readAllLines() needs the charset as a second argument, because the one-argument version came in Java 8.

Path recipe = Path.of("recipe.txt");

List<String> lines = Files.readAllLines(recipe);                 // [Pancakes, Mix flour, milk and one egg., Fry both sides.]
String joined = String.join("\n", lines);                        // "Pancakes\nMix flour, milk and one egg.\nFry both sides."

String streamed;
try (Stream<String> stream = Files.lines(recipe)) {
  streamed = stream.collect(Collectors.joining("\n"));           // same as joined
}

boolean same = streamed.equals(Files.readString(recipe));       // false, the last "\n" is gone

Both methods drop the line breaks, so the joined text ends without a line break, and every “\r\n” in a Windows file becomes the separator we chose. That is fine when we only care about the words. It is wrong when we compare the text with the original file or write it back, and in that case we use readString() or readAllBytes().

The readAllLines() method loads every line into memory, so it has the same size limit as readString(). The lines() method reads one line at a time, which makes it the right choice for large files, as we will see in section 9.

4. Using BufferedReader

A BufferedReader reads characters from a file in chunks and gives them to us one line at a time with readLine(). We control the loop, so we can stop early or skip lines while we build the text in a StringBuilder. The reader does not close itself, so we always open it in a try-with-resources block.

The following example opens the reader with Files.newBufferedReader(), which takes a Path and a charset. The older new BufferedReader(new FileReader(file)) does the same, and since Java 11 the FileReader constructor also accepts a charset.

Path recipe = Path.of("recipe.txt");
StringBuilder builder = new StringBuilder();

try (BufferedReader reader = Files.newBufferedReader(recipe, StandardCharsets.UTF_8)) {
  String line;
  while ((line = reader.readLine()) != null) {
    builder.append(line).append('\n');
  }
}

String text = builder.toString();                                // "Pancakes\nMix flour, milk and one egg.\nFry both sides.\n"

Since Java 8, the reader also has a lines() method that returns a Stream<String>, so the loop becomes one line. The stream reads from the reader, so the try-with-resources block stays.

String text;
try (BufferedReader reader = Files.newBufferedReader(Path.of("recipe.txt"))) {
  text = reader.lines().collect(Collectors.joining("\n"));       // "Pancakes\nMix flour, milk and one egg.\nFry both sides."
}

We use a BufferedReader on Java 8 or older, and whenever we need to stop reading at some line. It is also the right tool when the input is not a file but a socket or a process output.

5. Using Scanner

A Scanner splits input into tokens at a delimiter, which is whitespace by default. To get the whole file as one token, we set the delimiter to the regular expression “\\A”, which matches the start of the input and nothing else. The first call to next() then returns everything up to the end of the file.

The following example reads the file with a UTF-8 charset. The hasNext() check matters, because next() throws a NoSuchElementException on an empty file. The constructor that takes a Path and a Charset came in Java 10, so on older versions we pass a File and the charset name “UTF-8” instead.

String text;
try (Scanner scanner = new Scanner(Path.of("recipe.txt"), StandardCharsets.UTF_8)) {
  text = scanner.useDelimiter("\\A").hasNext() ? scanner.next() : "";   // "Pancakes\nMix flour, milk and one egg.\nFry both sides.\n"
}

The Scanner keeps the line breaks as they are in the file. We pick it when the same code also parses numbers or words from the input, otherwise readString() is shorter.

6. Using FileInputStream

A FileInputStream gives us the raw bytes of a file. Since Java 9, every InputStream) has a readAllBytes() method, so converting the stream to a String is the same two steps as in section 2. The stream is a resource, so it goes into a try-with-resources block.

String text;
try (InputStream in = new FileInputStream("recipe.txt")) {
  text = new String(in.readAllBytes(), StandardCharsets.UTF_8);   // "Pancakes\nMix flour, milk and one egg.\nFry both sides.\n"
}

We need the stream version when an API hands us an InputStream instead of a path, for example an uploaded file in a web controller or an entry in a zip file.

7. Reading a File from the Classpath

Files under src/main/resources are packaged into the jar, so they have no file path at runtime and Path.of(“recipe.txt”) does not find them. We read a file from the resources folder through the class loader, which returns an InputStream or null when the resource does not exist.

The following method reads a resource into a String. It throws an IllegalArgumentException with the resource name when the file is missing, which is a clearer error than the NullPointerException we get otherwise.

static String readResource(String name) {
  try (InputStream in = ReadFileToString.class.getClassLoader().getResourceAsStream(name)) {
    if (in == null) {
      throw new IllegalArgumentException(name + " is not on the classpath");
    }
    return new String(in.readAllBytes(), StandardCharsets.UTF_8);
  } catch (IOException e) {
    throw new UncheckedIOException(e);
  }
}

String resource = readResource("recipe.txt");   // "Pancakes\nMix flour, milk and one egg.\nFry both sides.\n"

The name is relative to the root of the classpath, so a file at src/main/resources/data/recipe.txt is read with “data/recipe.txt”. The classpath article covers the getResource() variant that returns a URL.

8. Charset and Line Separator Pitfalls

Reading a file into a String has two steps that go wrong in production even when the code looks right. The bytes must be decoded with the charset the file was written in, and the line breaks in the file may not be the ones our code expects.

8.1. Wrong Charset

When the file was written in a different charset than the one we read with, we get either an exception or wrong characters. UTF-8 is strict, so reading a Latin-1 file that has accented characters throws a MalformedInputException. Reading a UTF-8 file as Latin-1 does not throw and gives two wrong characters for each accented one instead.

The following example writes “Creme brulee” with accents in ISO-8859-1 and reads it back both ways. The accented letters are written as Unicode escapes to keep the source file ASCII.

Path latin = Path.of("latin.txt");
Files.writeString(latin, "Cr\u00e8me br\u00fbl\u00e9e", StandardCharsets.ISO_8859_1);

try {
  String wrong = Files.readString(latin);                                  // MalformedInputException: Input length = 3
} catch (MalformedInputException e) {
  System.out.println("Not UTF-8: " + e.getMessage());
}

String right = Files.readString(latin, StandardCharsets.ISO_8859_1);      // 12 chars, "Creme brulee" with accents

The fix is to know the charset of the file and pass it. Since Java 18, the default charset is UTF-8 on every platform, so FileReader and Scanner without a charset argument also read UTF-8. On Java 17 and older, the default depends on the operating system, so pass the charset in every call. The UTF-8 article shows the same rules for writing.

8.2. Line Separators

Windows files end each line with “\r\n”, and Linux and macOS files use “\n”. The readString(), readAllBytes(), Scanner and FileInputStream methods keep whatever is in the file. The line-based methods (readAllLines(), Files.lines(), BufferedReader.readLine() and BufferedReader.lines()) recognize both kinds and strip them.

Path windows = Path.of("windows.txt");
Files.writeString(windows, "Pancakes\r\nFry both sides.\r\n");

String raw = Files.readString(windows);                          // keeps "\r\n"
boolean hasCrLf = raw.contains("\r\n");                          // true

String normalized = String.join("\n", Files.readAllLines(windows));   // "Pancakes\nFry both sides."
String[] parts = raw.split("\\R");                               // 2 parts, "\\R" matches any line break

When the text goes into a comparison or a hash, make the line breaks the same first, either by joining the lines or by splitting the raw text on the \\R pattern, which matches any kind of line break. When the text is written back to a file, keep the raw version so the file does not change its line endings.

9. Reading Large Files

A String needs at least as much heap as the file has characters, and readAllBytes() needs the byte array on top of that for a moment. A 500 MB log file in a JVM with a 1 GB heap either throws an OutOfMemoryError or leaves no room for the rest of the app. The large file article compares the options in detail.

The fix is not to build the String at all. The Files.lines() stream reads one line at a time, so the memory stays flat no matter how long the file is. The following example counts the matching lines in a file with 200,000 lines without ever holding the whole file.

Path big = Path.of("big.txt");

long matches;
try (Stream<String> stream = Files.lines(big)) {
  matches = stream.filter(line -> line.contains("Fry")).count();   // 200000
}
long size = Files.size(big);                                         // 3200000 bytes

The BufferedReader loop from section 4 does the same when we need to stop at some line. When the file has no lines at all, for example a binary file, read it in chunks with InputStream.read(byte[]) or a FileChannel, which the large file article also shows.

10. Using Commons IO and Guava

Projects that already depend on Apache Commons IO or Guava can read a file in one line with a File instead of a Path. Both methods close the file themselves and need the charset as an argument. We do not add either library only for reading a file, because Files.readString() does the same with no dependency.

The example uses Commons IO 2.22.0 and Guava 33.7.2-jre.

File file = new File("recipe.txt");

String commons = FileUtils.readFileToString(file, StandardCharsets.UTF_8);         // "Pancakes\nMix flour, ... sides.\n"

String guava = com.google.common.io.Files.asCharSource(file, StandardCharsets.UTF_8).read();   // same text

11. Which Method Should We Use?

All eight methods return the same text for a small UTF-8 file, so the choice depends on the Java version and the file size, and on whether the line breaks must survive. In most apps, Files.readString() is the answer, and the other methods cover the cases where it does not fit.

MethodSinceKeeps line breaksCloses the fileUse it when
Files.readString()Java 11YesYesThe default choice for a file that fits in memory
Files.readAllBytes() + new String()Java 7YesYesJava 7 to 10, or we need the bytes anyway
Files.readAllLines() + String.join()Java 7 (Java 8 without a charset)No, rejoinedYesWe also need the lines as a list
Files.lines() + Collectors.joining()Java 8No, rejoinedOn stream closeWe filter or map the lines before joining
BufferedReader.readLine()Java 1.1No, we append our ownWe close itWe stop early or read from a socket
Scanner with “\\A”Java 5 (Java 10 with a Path)YesWe close itThe same code also parses tokens
FileInputStream.readAllBytes()Java 9YesWe close itThe API gives us an InputStream
getResourceAsStream()Java 1.1YesWe close itThe file is inside the jar

The diagram shows the same decision as a flow. We start from where the file is and how big it is, and the first “yes” gives the method.

Decision flow that picks the Java method for reading a file to String by classpath, file size, Java version and line break needs
In most apps the answer is Files.readString(), and large files are streamed with Files.lines()

12. Java Read File to String FAQs

12.1. How Do We Read a File to String in Java 8?

Use Files.readAllBytes() with the String constructor, because readString() came in Java 11. The call new String(Files.readAllBytes(path), StandardCharsets.UTF_8) keeps the line breaks and closes the file itself. When we only need the lines, Files.lines() with Collectors.joining(“\n”) is the Java 8 stream version, as shown in section 3.

12.2. Does Files.readString() Close the File?

Yes. The method closes the file after it has read all bytes, and it also closes the file when an IOException is thrown halfway. We close Files.lines(), BufferedReader, Scanner and the input streams ourselves, which is why those examples sit in a try-with-resources block.

12.3. What Is the Largest File We Can Read into a String?

A String holds at most about 2 billion characters, so the hard limit is around 2 GB, and readString() throws an OutOfMemoryError before that when the heap is smaller. In practice, the limit is the heap we can give the app. Anything larger than a few hundred megabytes is better read line by line with Files.lines(), as in section 9.

13. Conclusion

The Files.readString() method reads a file to String in one call on Java 11 and later. It decodes the bytes as UTF-8 by default and closes the file itself. On older versions, Files.readAllBytes() with the String constructor gives the same text.

The methods that return lines, such as readAllLines() and lines(), drop the line breaks, so we join them with a separator and accept that “\r\n” turns into “\n”. Pass the charset when the file is not UTF-8, and stream the lines instead of building the String when the file is larger than the memory we want to spend.

14. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.