Reading and Writing Files in Java

Introduction

When programming, whether you're creating a mobile app, a web application, or just writing scripts, you often have the need to read or write data to a file in your application. This data could be cache data, data you retrieved for a dataset, an image, or just about anything else you can think of.

In this tutorial, we are going to show the most common ways you can read and write to files in Java.

Java provides several API (also known as Java I/O) to read and write files since its initial releases. With subsequent releases, Java I/O has been improved, simplified and enhanced to support new features.

Before we get in to some actual examples, it would help to understand the classes available to you that will handle the reading and writing of data to files. In the following sections we'll provide a brief overview of the Java I/O classes and explain what they do, then we'll take a look at Java NIO Streams, and finally we'll show some examples of reading and writing data to files.

I/O Streams

There are two types of Streams you can use to interact with files:

  1. Character Streams
  2. Byte Streams

For each of the above stream types, there are several supporting classes shipped with Java, which we'll take a quick look at below.

Character Streams

Character Streams are used to read or write the characters data type. Let's look at the most commonly used classes. All of these classes are defined under java.io package.

Here are some classes you should know that can be used to read character data:

  • Reader: An abstract class to read a character stream.
  • InputStreamReader: Class used to read the byte stream and converts to character stream.
  • FileReader: A class to read the characters from a file.
  • BufferedReader: This is a wrapper over the Reader class that supports buffering capabilities. In many cases this is most preferable class to read data because more data can been read from the file in one read() call, reducing the number of actual I/O operations with file system.

And here are some classes you can use to write character data to a file:

  • Writer: This is an abstract class to write the character streams.
  • OutputStreamWriter: This class is used to write character streams and also convert them to byte streams.
  • FileWriter: A class to actually write characters to the file.
  • BufferedWriter: This is a wrapper over the Writer class, which also supports buffering capabilities. This is most preferable class to write data to a file since more data can be written to the file in one write() call. And like the BufferedReader, this reduces the number of total I/O operations with file system.

Byte Streams

Byte Streams are used to read or write byte data with files. This is different from before in the way they treat the data. Here you work with raw bytes, which could be characters, image data, unicode data (which takes 2 bytes to represent a character), etc.

In this section we'll take a look at the most commonly used classes. All of these classes are defined under java.io package.

Here are the classes used to read the byte data:

  • InputStream: An abstract class to read the byte streams.
  • FileInputStream: A class to simply read bytes from a file.
  • BufferedInputStream: This is a wrapper over InputStream that supports buffering capabilities. As we saw in the character streams, this is a more efficient method than FileInputStream.

And here are the classes used to write the byte data:

  • OutputStream: An abstract class to write byte streams.
  • FileOutputStream: A class to write raw bytes to the file.
  • ByteOutputStream: This class is a wrapper over OutputStream to support buffering capabilities. And again, as we saw in the character streams, this is a more efficient method than FileOutputStream thanks to the buffering.

Java NIO Streams

Java NIO is a non-blocking I/O API which was introduced back in Java 4 and can be found in the java.nio package. In terms of performance, this is a big improvement in the API for I/O operations. Buffers, Selectors, and Channels are the three primary components of Java NIO, although in this article we'll focus strictly on using the NIO classes for interacting with files, and not necessarily the concepts behind the API.

As this tutorial is about reading and writing files, we will discuss only the related classes in this short section:

  • Path: This is a hierarchical structure of an actual file location and is typically used to locate the file you want to interact with.
  • Paths: This is a class that provides several utility methods to create a Path from a given string URI.
  • Files: This is another utility class which has several methods to read and write files without blocking the execution on threads.

Using these few classes, you can easily interact with files in a more efficient way.

The Difference Between Java I/O and NIO

The main difference between these two packages is that the read() and write() methods of Java IO are a blocking calls. By this we mean that the thread calling one of these methods will be blocked until the data has been read or written to the file.

On the other hand, in the case of NIO, the methods are non-blocking. This means that the calling threads can perform other tasks (like reading/writing data from another source or update the UI) while the read or write methods wait for their operation to complete. This can result in a significant performance increase if you're dealing with many IO requests or lots of data.

Examples of Reading and Writing Text Files

In the above sections, we have discussed the different APIs provided by Java and now it's finally time to use these API classes in some code.

The example code below handles reading and writing text files using the various classes we detailed above. To simplify things, and provide a better comparison of the actual methods being used, the input and output are going to remain the same between the examples.

Note: To avoid any confusion on the file path, the example code will read and write from a file on in user's home directory. The user's home directory can be found using System.getProperty("user.home");, which is what we use in our examples.

Example 1

Using FileReader and FileWriter classes:

String directory = System.getProperty("user.home");  
String fileName = "sample.txt";  
String absolutePath = directory + File.separator + fileName;

// write the content in file 
try(FileWriter fileWriter = new FileWriter(absolutePath)) {  
    String fileContent = "This is a sample text.";
    fileWriter.write(fileContent);
} catch (IOException e) {
    // exception handling
}

// read the content from file
try(FileReader fileReader = new FileReader(absolutePath)) {  
    int ch = fileReader.read();
    while(ch != -1) {
        System.out.print((char)ch);
        ch = fileReader.read();
    }
} catch (FileNotFoundException e) {
    // exception handling
} catch (IOException e) {
    // exception handling
}
Example 2

Using BufferedReader and BufferedWriter classes:

String directory = System.getProperty("user.home");  
String fileName = "sample.txt";  
String absolutePath = directory + File.separator + fileName;

// write the content in file 
try(BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(absolutePath))) {  
    String fileContent = "This is a sample text.";
    bufferedWriter.write(fileContent);
} catch (IOException e) {
    // exception handling
}

// read the content from file
try(BufferedReader bufferedReader = new BufferedReader(new FileReader(absolutePath))) {  
    String line = bufferedReader.readLine();
    while(line != null) {
        System.out.println(line);
        line = bufferedReader.readLine();
    }
} catch (FileNotFoundException e) {
    // exception handling
} catch (IOException e) {
    // exception handling
}
Example 3

Using FileInputStream and FileOutputStream classes:

String directory = System.getProperty("user.home");  
String fileName = "sample.txt";  
String absolutePath = directory + File.separator + fileName;

// write the content in file 
try(FileOutputStream fileOutputStream = new FileOutputStream(absolutePath)) {  
    String fileContent = "This is a sample text.";
    fileOutputStream.write(fileContent.getBytes());
} catch (FileNotFoundException e) {
    // exception handling
} catch (IOException e) {
    // exception handling
}

// reading the content of file
try(FileInputStream fileInputStream = new FileInputStream(absolutePath)) {  
    int ch = fileInputStream.read();
    while(ch != -1) {
        System.out.print((char)ch);
        ch = fileInputStream.read();
    }
} catch (FileNotFoundException e) {
    // exception handling
} catch (IOException e) {
    // exception handling
}
Example 4

Using BufferedInputStream and BufferedOutputStream classes:

String directory = System.getProperty("user.home");  
String fileName = "sample.txt";  
String absolutePath = directory + File.separator + fileName;

// write the content in file 
try(BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(absolutePath))) {  
    String fileContent = "This is a sample text.";
    bufferedOutputStream.write(fileContent.getBytes());
} catch (IOException e) {
    // exception handling
}

// read the content from file
try(BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(absolutePath))) {  
    int ch = bufferedInputStream.read();
    while(ch != -1) {
        System.out.print((char)ch);
        ch = bufferedInputStream.read();
    }
} catch (FileNotFoundException e) {
    // exception handling
} catch (IOException e) {
    // exception handling
}
Example 5

Using the java.nio classes:

String directory = System.getProperty("user.home");  
String fileName = "sample.txt";

String content = "This is a sample text.";  
Path path = Paths.get(directory, fileName);

try {  
    Files.write(path, content.getBytes(), StandardOpenOption.CREATE);
} catch (IOException e) {
    // exception handling
}

try {  
    List<String> list = Files.readAllLines(path);
    list.forEach(line -> System.out.println(line));
} catch (IOException e) {
    // exception handling
}

Another way to retrieve the content via the Files class, which is more important if you're not reading text data, is to use the readAllBytes method to read the data in to a byte array:

try {  
    byte[] data = Files.readAllBytes(path);
    System.out.println(new String(data));
} catch (IOException e) {
    // exception handling
}

In case you are interested in using streams with java.nio, you can also use the below methods provided by the Files class, which work just like the streams we covered earlier in the article:

Files.newBufferedReader(path)  
Files.newBufferedWriter(path, options)  
Files.newInputStream(path, options)  
Files.newOutputStream(path, options)  

Conclusion

In this article we have covered the most common ways to read and write data to a file using both the Java I/O package and the newer Java NIO package. Whenever possible, we recommend to use the Java NIO classes for file operations due to its non-blocking API, and in addition the code is a bit more maintainable and readable.