Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

File handling is an essential aspect we frequently encounter. When it comes to writing data to files, the FileWriter class is commonly used. Within this class, two important methods, flush() and close(), play distinct roles in managing file output streams.

In this tutorial, we’ll address FileWriter‘s common usage and delve into the differences between its flush() and close() methods.

2. FileWriter and try-with-resources

The try-with-resources statement in Java is a powerful mechanism for managing resources, especially when dealing with I/O operations such as file handling. It automatically closes the resources specified in its declaration once the code block is exited, whether normally or due to an exception.

However, there might be situations where using FileWriter with try-with-resources is not ideal or necessary.

FileWriter may behave differently with or without try-with-resources. Next, let’s take a closer look at it.

2.1. Using FileWriter With try-with-resources

If we use FileWriter with try-with-resources, the FileWriter object gets flushed and closed automatically when we exit the try-with-resources block.

Next, let’s show this in a unit test:

@Test
void whenUsingFileWriterWithTryWithResources_thenAutoClosed(@TempDir Path tmpDir) throws IOException {
    Path filePath = tmpDir.resolve("auto-close.txt");
    File file = filePath.toFile();

    try (FileWriter fw = new FileWriter(file)) {
        fw.write("Catch Me If You Can");
    }

    List<String> lines = Files.readAllLines(filePath);
    assertEquals(List.of("Catch Me If You Can"), lines);
}

Since our test will write and read files, we employed JUnit5’s temporary directory extension (@TempDir). With this extension, we can concentrate on testing the core logic without manually creating and managing temporary directories and files for testing purposes.

As the test method shows, we write a string in the try-with-resources block. Then, when we check the file content using Files.readAllLines()we get the expected result.

2.2. Using FileWriter Without try-with-resources

However, when we use FileWriter without try-with-resources, the FileWriter object won’t automatically get flushed and closed:

@Test
void whenUsingFileWriterWithoutFlush_thenDataWontBeWritten(@TempDir Path tmpDir) throws IOException {
    Path filePath = tmpDir.resolve("noFlush.txt");
    File file = filePath.toFile();
    FileWriter fw = new FileWriter(file);
    fw.write("Catch Me If You Can");

    List<String> lines = Files.readAllLines(filePath);
    assertEquals(0, lines.size());
    fw.close(); //close the resource
}

As the test above shows, although we wrote some text to the file by calling FileWriter.write(), the file was still empty.

Next, let’s figure out how to solve this problem.

3. FileWriter.flush() and FileWriter.close()

In this section, let’s first solve the “file is still empty” problem. Then, we’ll discuss the difference between FileWriter.flush() and FileWriter.close().

3.1. Solving “The File Is Still Empty” Problem

First, let’s understand quickly why the file is still empty after we called FileWriter.write(). 

When we invoke FileWriter.write(), the data is not immediately written to the file on the disk. Instead, it is temporarily stored in a buffer. Consequently, to visualize the data in the file, it is necessary to flush the buffered data to the file.

The straightforward way is to call the flush() method:

@Test
void whenUsingFileWriterWithFlush_thenGetExpectedResult(@TempDir Path tmpDir) throws IOException {
    Path filePath = tmpDir.resolve("flush1.txt");
    File file = filePath.toFile();
    
    FileWriter fw = new FileWriter(file);
    fw.write("Catch Me If You Can");
    fw.flush();

    List<String> lines = Files.readAllLines(filePath);
    assertEquals(List.of("Catch Me If You Can"), lines);
    fw.close(); //close the resource
}

As we can see, after calling flush(), we can obtain the expected data by reading the file.

Alternatively, we can call the close() method to transfer the buffered data to the file. This is because close() first performs flush, then closes the file stream writer.

Next, let’s create a test to verify this:

@Test
void whenUsingFileWriterWithClose_thenGetExpectedResult(@TempDir Path tmpDir) throws IOException {
    Path filePath = tmpDir.resolve("close1.txt");
    File file = filePath.toFile();
    FileWriter fw = new FileWriter(file);
    fw.write("Catch Me If You Can");
    fw.close();

    List<String> lines = Files.readAllLines(filePath);
    assertEquals(List.of("Catch Me If You Can"), lines);
}

So, it looks pretty similar to the flush() call. However, the two methods serve different purposes when handling file output streams.

Next, let’s have a close look at their differences.

3.2. The Difference Between the flush() and close() Methods

The flush() method is primarily used to force any buffered data to be written immediately without closing the FileWriter, while the close() method both performs flushing and releases associated resources.

In other words, invoking flush() ensures that the buffered data is promptly written to disk, allowing continued write or append operations to the file without closing the stream. Conversely, when close() is called, it writes the existing buffered data to the file and then closes it. Consequently, further data cannot be written to the file unless a new stream is opened, such as by initializing a new FileWriter object.

Next, let’s understand this through some examples:

@Test
void whenUsingFileWriterWithFlushMultiTimes_thenGetExpectedResult(@TempDir Path tmpDir) throws IOException {
    List<String> lines = List.of("Catch Me If You Can", "A Man Called Otto", "Saving Private Ryan");
    Path filePath = tmpDir.resolve("flush2.txt");
    File file = filePath.toFile();
    FileWriter fw = new FileWriter(file);
    for (String line : lines) {
        fw.write(line + System.lineSeparator());
        fw.flush();
    }

    List<String> linesInFile = Files.readAllLines(filePath);
    assertEquals(lines, linesInFile);
    fw.close(); //close the resource
}

In the above example, we called write() three times to write three lines to the file. After each write() call, we invoked flush(). In the end, we can read the three lines from the target file.

However, if we attempt to write data after calling FileWriter.close(), IOException with the error message “Stream closed” will raise:

@Test
void whenUsingFileWriterWithCloseMultiTimes_thenGetIOExpectedException(@TempDir Path tmpDir) throws IOException {
    List<String> lines = List.of("Catch Me If You Can", "A Man Called Otto", "Saving Private Ryan");
    Path filePath = tmpDir.resolve("close2.txt");
    File file = filePath.toFile();
    FileWriter fw = new FileWriter(file);
    //write and close
    fw.write(lines.get(0) + System.lineSeparator());
    fw.close();

    //writing again throws IOException
    Throwable throwable = assertThrows(IOException.class, () -> fw.write(lines.get(1)));
    assertEquals("Stream closed", throwable.getMessage());
}

4. Conclusion

In this article, we explored FileWriter‘s common usage. Also, we discussed the difference between FileWriter‘s flush() and close() methods.

As always, the complete source code for the examples is available over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.