
Java programmer expert
Java file handling is a fundamental aspect of programming. There are many different ways to read files in Java. Just by looking for a Java files example for simple file reading you may come across Java classes like InputStream, FileInputStream, DataInputStream, SequenceInputStream, Reader, InputStreamReader, FileReader, BufferedReader, FileChannel, SeekableByteChannel, Scanner, StreamTokenizer, Files and others.
We’re pretty sure there are even more classes available, and some didn’t make this list. Of course, we haven’t yet mentioned third-party external libraries for working with files, which are also an option.
Most of the pre-defined classes for reading and writing files in Java are located in the java.io and java.nio.file packages. With the introduction of the new Java NIO.2 (New I/O) file API, the situation became even more complex, and how to work with files efficiently is often a common question on programming forums — even among experienced developers.
When working with files, we should know in advance what types of files we will be working with and therefore whether we need to read binary files (e.g. music in mp3 format) or text files. We should know whether the files are small enough to load entirely into memory or so large that they require streamed processing (e.g., line by line).
Each of the file handling classes mentioned above has its use for specific cases. In general, however, we use binary data retrieval Stream classes and text Reader classes. Classes that have both of these expressions in their name combine binary and text data retrieval. For example, InputStreamReader consumes an InputStream, but behaves as Reader. FileReader is basically a combination of FileInputStream andInputStreamReader.
As we can see, reading data in Java can get messy. So, in this article, we’ll focus on three common scenarios that cover 90% of use cases:
Before Java 7, reading files was cumbersome. The most common approach was using FileInputStream, which required manual resource cleanup (closing the stream in both success and error cases). Automatic resource management (via try-with-resources) didn’t exist yet, so many developers preferred third-party libraries like Apache Commons IO or Google Guava for simpler file operations.
This changed with Java 7, which introduced the NIO.2 File API, including the java.nio.file.Files helper class. This class provides convenient one-line methods for reading entire text/binary files.
Using the Files.readAllBytes() method, we can read the contents of the entire file into the byte array:
import java.nio.file.Files;
import java.nio.file.Path;
String fileName = "fileName.dat";
byte[] bytes = Files.readAllBytes(Path.of(fileName));
The Path class represents a file’s location in the filesystem.
Since Java 11, it is possible to simply read the contents of an entire text file into a variable of type String using the Files.readString() method as follows:
import java.nio.file.Files;
import java.nio.file.Path;
String fileName = "fileName.dat";
String text = Files.readString(Path.of(fileName));
The readString() method uses the readAllBytes() method internally, and then converts the binary data into the desired string of type String.
Text files usually consist of multiple lines. If we want to read and process the text line by line, we can use a method available since Java 8 – readAllLines(), which does this automatically.
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
String fileName = "fileName.dat";
List<String> lines = Files.readAllLines(Path.of(fileName));
Then we just iterate through the list and process each row.
Java 8 introduced streams as a significant language enhancement. The same version extended the Files class with a new lines() method that returns the read lines of a text file as a stream of strings of type String. This allows us to use the functionality of streams e.g. when filtering data.
import java.nio.file.Files;
import java.nio.file.Path;
String fileName = "fileName.dat";
Files.lines(Path.of(fileName))
.filter(line -> line.contains("ERROR"))
.forEach(System.out::println);
In this example, we will output to the console all the lines of the read file that contains the string “ERROR”.
These methods cover the most common scenarios for reading small files and share the characteristic that they are read entirely into RAM. For large files, it is advisable to read them in chunks and process them immediately. We will demonstrate this below.
The binary file is read via InputStream one byte at a time (until the end of the file when -1 is returned), which is quite long in the case of large files. This can be speeded up by reading data via BufferedInputStream, which wraps the FileInputStream class and reads data from the operating system no longer byte by byte, but in 8 KB blocks that are stored in memory. Subsequently, the reading of the file is done byte by byte, but it is much faster because it is done directly from memory.
import java.io.BufferedInputStream;
import java.io.FileInputStream;
String fileName = "fileName.dat";
try (FileInputStream is = new FileInputStream(fileName);
BufferedInputStream bis = new BufferedInputStream(is)) {
int b;
while ((b = bis.read()) != -1) {
// TODO: process b
}
}
Reading a file block by block (called buffering) is significantly faster than reading by bytes.
FileReader class combines FileInputStream a InputStreamReader. For faster files reading we use the class BufferedReader which wraps the class FileReader and allows to use an 8 KB buffer along with an additional buffer for 8192 decoded characters. The advantage of the BufferedReader class is that it allows us to read and process the text file line by line (instead of reading and processing on a character by character basis).
import java.io.BufferedReader;
import java.io.FileReader;
String fileName = "fileName.dat";
try (FileReader reader = new FileReader(fileName);
BufferedReader bufferedReader = new BufferedReader((reader))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println("Line: " + line);
}
}
Sometimes, instead of reading a file line by line, we need to read it in parts. Scanner works by dividing the contents of a file into parts using a separator, which can be any constant value. This class is commonly used for CSV (comma-separated values) files, which have a specific format where data is separated by commas. Such files can be used as tables in Excel applications.
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
List<String> words = new ArrayList<>();
String fileName = "fileName.csv";
Scanner scanner = new Scanner(Path.of(fileName));
scanner.useDelimiter(",");
while (scanner.hasNext()) {
String next = scanner.next();
words.add(next);
}
scanner.close();
In this example, we read the CSV file on a token-by-token basis (instead of the classic line-by-line reading approach) separated by commas and saved those in a list for further processing.
In this article, we have shown the most common scenarios of reading data from a file. Java makes it easy to handle small files – you can read and write them to memory with a single function call. And when it comes to reading and processing large files, Java offers efficient solutions using buffering classes.
If you’re a Java developer looking for work, check out our employee benefits and respond to our job offers!