Java: Read a File Into a String

Introduction

In this tutorial, we'll be reading a file into a String in Java. There are a few ways we can read textual contents of a file.

Here's a list of all the classes and methods we'll go over:

Files.lines()

The Files class contains static methods for working with files and directories. A useful method is lines() which returns a stream of strings: Stream<String>. From this stream, one can obtain lines contained in a file.

The method accepts a Path to the file we'd like to read with an optional Charset. We'll use try-with-resources syntax to automate flushing and closing:

Path path = Paths.get("input.txt");

try (Stream<String> stream = Files.lines(path, StandardCharsets.UTF_8)) {
	stream.forEach(System.out::println);
} catch (IOException ex) {
	// Handle exception
}

Since the method returns a Stream, we use its forEach() method to iterate over the lines, with a method reference for brevity.

Instead of printing each line, a StringBuilder can be used to append lines:

Path path = Paths.get("input.txt");

StringBuilder sb = new StringBuilder();

try (Stream<String> stream = Files.lines(path)) {
	stream.forEach(s -> sb.append(s).append("\n"));
	
} catch (IOException ex) {
	// Handle exception
}

String contents = sb.toString();

With StringBuilder, the entire file can be represented in a single String (the contents variable above). Before performing such iterations, it's important to consider the length of the input file.

If the file is not too large, it's okay to put it in a String, though, if it's hundreds of megabytes in size, it's not so wise.

Files.readString()

Since Java 11, the Files class introduced us to the readString() method, which accepts a Path to the file, as well as a Charset.

Unlike Files.lines(), it returns a String directly, instead of a Stream object:

Path path = Paths.get("input.txt");

String contents = null;
try {
	contents = Files.readString(path, StandardCharsets.ISO_8859_1);
} catch (IOException ex) {
	// Handle exception
}

Files.readAllBytes()

A more low-level approach to reading is the Files.readAllBytes() method, which returns a byte[]. It's up to the developer to use these bytes - convert them to a String, process them as they are, etc.

This method also accepts a Path to the file we'd like to read:

Path path = Paths.get("input.txt");

byte[] bytes = null;
try {
	bytes = Files.readAllBytes(path);
} catch (IOException ex) {
	// Handle exception
}

Now, the bytes array holds all the information from the input.txt file. The easiest way to convert it into a String is to put them in a constructor with an optional Charset:

String str = new String(bytes, StandardCharsets.UTF_8);

Note: Solutions like reading all bytes are only appropriate in circumstances where we're dealing with small file sizes. It's not performance-friendly and it doesn't make much sense to keep large files in the program's memory.

Scanner

Scanner is a particularly useful class for reading content from streams. Since it works with abstract streams, it can also be used for reading strings. Scanner works by breaking the input into tokens which are sequentially retrieved from the input stream.

Since we are working with strings, we would like to use methods that return strings. Scanner has next() and nextLine() exactly for that. Both methods return objects of type String. The former is used to read arbitrary strings, whereas the latter parses and returns entire lines.

If each line contains the right amount of data then nextLine() is an ideal choice. If there is important information in the file that's broken down into smaller chunks but not necessarily lines (or the file contains, say, a single line) then next() might be a better option.

Scanner's constructor accepts many objects - Paths, InputStreams, Files, etc. We'll use a File:

File file = new File("input.txt");
Scanner sc = new Scanner(file);

while(sc.hasNext()) {
	System.out.println(sc.next());
}

We're using a while loop as long as the sc has more elements. If we didn't check with hasNext(), sc would throw a NoSuchElementexception if we try accessing an element after the last one.

The idea of using hasNext() and next() methods come from the Iterator interface, as Scanner internally implements it.

FileReader

The FileReader is used to read files. It offers the read() and read(char[]) methods, which return a single character and multiple characters respectively. Also, it accepts a File or String into the constructor.

FileReader.read(char[])

Let's opn a file using FileReader and read its contents:

FileReader in = new FileReader("input.txt");

char[] chars = new char[256];
int n = in.read(chars, 0, chars.length);

String contents = new String(chars);

The read() method accepts a sequence of characters (that we're storing the read characters in), the starting point and the ending point of what we'd like to read. Specifically, we've decided to read 256 characters at most. If input.txt has more, we'll read only 256 characters. If it has less, the readable characters are returned.

The return value, stored inside integer n can be used to check how many characters the method actually read. In case the end of the stream has been reached, the method returns -1.

Since the method fills a char[], we can convert it into a String. A similar result can be obtained by using String.valueOf(char[]).

FileReader.read()

The read() method, without a char[] reads a single character at a time. We'll want to iterate through the contents and read each character ourselves:

FileReader in = new FileReader("input.txt");

StringBuilder sb = new StringBuilder();

while(in.read() != -1) {
	sb.append(in.read());
}

String contents = sb.toString();
in.close();

Here, we check if the read character isn't -1, which indicated that there are no characters left to read. If not, we append() it to a StringBuilder and finally, convert it to a String.

Note: Both read() and read(char[]) read bytes, convert them into characters, and return them one by one. This is inefficient and should be done with buffering when possible.

BufferedReader

BufferedReader is an object designed to read text from a character-input stream. It is buffered, meaning that it uses an internal buffer for temporary storage. As we've seen in the previous section, "regular" Readers can sometimes be inefficient.

It's advised to wrap any potentially costly Reader into a BufferedReader to increase performance since buffering characters enables more efficient reading of the input text.

Let's instantiate a BufferedReader:

BufferedReader in = new BufferedReader(new FileReader("input.txt"));

At this point, we have a buffered reader object ready to read contents from input.txt. In this example, we'll be reading the file line-by-line, although BufferedReader supports reading single characters individually and also multiple characters into an array.

Let's use this BufferedReader instance to read a file and store its contents, line-by-line, into a String:

StringBuilder sb = new StringBuilder();

while(in.readLine != null) {
	sb.append(in.readLine()).append("\n");
}

String contents = sb.toString();
in.close();

Once again, we are using StringBuilder to collect all the lines. To separate each line, we append null-terminator (\n) between them. Finally, we close the stream.

Conclusion

In this article, we went over some common techniques for reading files into strings in Java. There are many options but most of them have a similar core principle: provide a path to the file, read the contents into a data structure (e.g. char[] or a String); then perform some final processing to collect all file contents in an appropriate manner.

We've covered the File.lines() method, the Files.readString() method, the Files.readAllBytes() method, as well as the Scanner, FileReader and BufferedReader classes.

Author image
About Luka Čupić
Croatia Website