Sometimes we want to read a file line by line to a string to process the content. A good example is reading a CSV file line by line and then splitting the line by comma (,) into multiple columns.

In Java, there are various options available to choose from when you need to read a file line by line.

Scanner

The Scanner class presents the simplest way to read a file line by line in Java. We can use Scanner class to open a file and then read its content line by line.

A Scanner breaks its input into tokens using a delimiter pattern, which is newline in our case:

try {
    // open file to read
    Scanner scanner = new Scanner(new File("examplefile.txt"));

    // read until end of file (EOF)
    while (scanner.hasNextLine()) {
        System.out.println(scanner.nextLine());
    }

    // close the scanner
    scanner.close();
		
} catch (FileNotFoundException ex) {
    ex.printStackTrace();
}

The hasNextLine() method returns true if there is another line in the input of this scanner without advancing the file read position.

To read data and move on to the next line, we should use the nextLine() method. This method moves the scanner past the current line and returns the rest of the current line, excluding any line separator at the end. The read position is then set to the beginning of the next line.

Since the nextLine() method continues to search through the input looking for a line separator, it may buffer all of the input searching for the line to skip if no line separators are present.

BufferedReader

The BufferedReader class provides an efficient way to read characters, arrays, and lines from a character-input stream.

As the name suggests, it buffers the characters up to of 8MB (or 8192KB) which is large enough for most use cases. If the file you are reading is larger than the default buffer size, you can customize the default size:

BufferedReader br = new BufferedReader(new FileReader("foo.txt"), size);

The BufferedReader constructor accepts a Reader instance (like FileReader, InputStreamReader) as character-input stream source. Here is a simple example that shows how to use it for reading a file line by line:

try {
    // create a reader instance
    BufferedReader br = new BufferedReader(new FileReader("examplefile.txt"));

    // read until end of file
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }

    // close the reader
    br.close();
		
} catch (IOException ex) {
    ex.printStackTrace();
}

The readLine() method reads a line of text from the file and returns a string containing the contents of the line, excluding any line-termination characters or null.

A null value does not mean that the string is empty. Rather it shows that the end of the file is reached.

Alternatively, you can use lines() method from BufferedReader class that returns a Stream of lines. You can easily convert this stream into a list or read the lines like the following:

try {
    // create a reader instance
    BufferedReader br = new BufferedReader(new FileReader("examplefile.txt"));

    // list of lines
    List<String> list = new ArrayList<>();

    // convert stream into list
    list = br.lines().collect(Collectors.toList());

    // read the lines
    list.forEach(System.out::println);

    // close the reader
    br.close();
		
} catch (IOException ex) {
    ex.printStackTrace();
}

Java 8 Streams

Java 8 Streams is another way (albeit cleaner) of reading a file line by line. We can use Files.lines() static method to initialize a lines stream like below:

try {
    // initialize lines stream
    Stream<String> stream = Files.lines(Paths.get("examplefile.txt"));

    // read lines
    stream.forEach(System.out::println);
		
} catch (IOException ex) {
    ex.printStackTrace();
}

In addition to simple API, streams are very useful for filtering, sorting and processing the data. Let us extend the above example and filter out the lines that end with a colon (:), then sort them alphabetically, and convert to uppercase:

try {
    // initialize lines stream
    Stream<String> stream = Files.lines(Paths.get("examplefile.txt"));

    // apply filter & sorting
    stream.filter(l -> l.endsWith(":"))
            .sorted()
            .map(String::toUpperCase)
            .forEach(System.out::println);
						
} catch (IOException ex) {
    ex.printStackTrace();
}

RandomAccessFile

The RandomAccessFile class provides a non-blocking mode of reading and writing files. A random access file behaves like a large array of bytes stored in the file system.

We can use RandomAccessFile to open a file in read mode and then use its readLine() method to read line by line:

try {
    // open file in read mode
    RandomAccessFile file = new RandomAccessFile("examplefile.txt", "r");
    // read until end of file
    String line;
    while ((line = file.readLine()) != null) {
        System.out.println(line);
    }

    // close the file
    file.close();
		
} catch (IOException ex) {
    ex.printStackTrace();
}

Apache Commons IO

The Apache Commons IO library contains utility classes, stream implementations, file filters, file comparators, and much more. Add the following to your build.gradle file to import the library in your project:

implementation 'commons-io:commons-io:2.6'

If you are using Maven, add the following to your pom.xml file:

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

We can now use FileUtils.readLines() static method from Apache Commons IO that reads all lines from a file into a List<String>:

try {
    // read all lines of a file
    List<String> lines = FileUtils.readLines(Paths.get("examplefile.txt").toFile(), "UTF-8");

    // process the lines
    for (String line : lines) {
        System.out.println(line);
    }

} catch (IOException ex) {
    ex.printStackTrace();
}

Since Apache Commons IO reads all lines from the file at once, it may not be a good solution for reading large files. It will continue blocking the for loop execution in above case until all lines are added to lines object.

Okie

Okie is another open-source I/O library developed by Square for Android, Kotlin, and Java. It complements native java.io and java.nio packages to make it much easier to access, store, and process the data.

To import Okie in your project, add the following to the build.gradle file:

implementation 'com.squareup.okio:okio:2.4.0'

If you are using Maven, add the following to your pom.xml file:

<dependency>
    <groupId>com.squareup.okio</groupId>
    <artifactId>okio</artifactId>
    <version>2.4.0</version>
</dependency>

Now we can use Okio.source() method to open a source stream to read a file. The returned Source interface is very small and has limited uses. Okie provides BufferedSource class to wrap the source with a buffer that makes your program run faster.

Let us have an example:

try {
    // open a source stream
    Source source = Okio.source(Paths.get("examplefile.txt").toFile());

    // wrap stream with a buffer
    BufferedSource bs = Okio.buffer(source);

    // read until end of file
    String line;
    while ((line = bs.readUtf8Line()) != null) {
        System.out.println(line);
    }

    // close the stream
    source.close();

} catch (IOException ex) {
    ex.printStackTrace();
}

The readUtf8Line() method reads the data until the next line delimiter – either \n, \r\n, or the end of the file. It returns that data as a string, omitting the delimiter at the end. When it encounters empty lines, the method will return an empty string. If there isn't no more data to read, it will return null.

✌️ Like this article? Follow @attacomsian on Twitter. You can also follow me on LinkedIn and DEV. Buy me a coffee (cost $3)

Need help to start a new Spring Boot or MEAN stack project? I am available for contract work. Hire me to accomplish your business goals with engineering and design. Let’s talk about your project: hi@attacomsian.com.