Java - 10+ Amazing Ways to Write to File
It's surprising to see how many different ways exist to write a File in Java! It can be quite confusing... This is why we made this tutorial. We'll explore the different ways, the best practices involved as well as the common pitfalls.
If you're wondering how to write text or binary data in a File, you're in the right place!
Input Streams¶
Yet another way to write data into a File in Java is using streams. This is by far the most widely used way to write content into files, because it's easy.
package com.octoperf;
import com.google.common.io.Closer;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import static com.google.common.base.Charsets.UTF_8;
public class JavaWriteFileTest {
private static final String HELLO_WORLD = "Hello World!";
private static final String OUT_TXT = "out.txt";
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Test
public void inputOutputTryFinally() throws IOException {
final File file = folder.newFile(OUT_TXT);
BufferedOutputStream out = null;
try {
out = new BufferedOutputStream(new FileOutputStream(file));
out.write(HELLO_WORLD.getBytes(UTF_8));
} finally {
if (out != null) {
out.close();
}
}
}
@Test
public void inputOutputCloser() throws IOException {
final File file = folder.newFile(OUT_TXT);
final Closer closer = Closer.create();
try {
final BufferedOutputStream out = closer.register(new BufferedOutputStream(new FileOutputStream(file)));
out.write(HELLO_WORLD.getBytes(UTF_8));
} finally {
closer.close();
}
}
@Test
public void inputOutputTryResources() throws IOException {
final File file = folder.newFile(OUT_TXT);
try (final BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
out.write(HELLO_WORLD.getBytes(UTF_8));
}
}
}
By using BufferedOutputStream
and FileOutputStream
, we are able to write the string Hello World!
into the File.
Please note it's recommended to avoid using the following code: out.write(HELLO_WORLD.getBytes());
. It relies on the platform default encoding to convert the string into bytes which can result in issues if the encoding changes.
The written file will contain the string Hello World!
.
As you can see, we need to convert the string to bytes using the code HELLO_WORLD.getBytes(UTF_8)
. This is not very elegant and can be further improved using a DataOutputStream:
@Test
public void dataOutputTryFinally() throws IOException {
final File file = folder.newFile(OUT_TXT);
DataOutputStream out = null;
try {
out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
out.writeUTF(HELLO_WORLD);
} finally {
if (out != null) {
out.close();
}
}
}
Now, we can use the code out.writeUTF(HELLO_WORLD);
to write the string in UTF-8 encoded bytes directly.
BufferedWriter¶
First, let's examine how to use the BufferedWriter
by examining this JUnit:
package com.octoperf;
import com.google.common.io.Closer;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class JavaWriteFileTest {
private static final String HELLO_WORLD = "Hello World!";
private static final String OUT_TXT = "out.txt";
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Test
public void bufferedWriterTryFinally() throws IOException {
final File file = folder.newFile(OUT_TXT);
Writer writer = null;
try {
writer = new BufferedWriter(new FileWriter(file));
writer.write(HELLO_WORLD);
} finally {
if (writer != null) {
writer.close();
}
}
}
@Test
public void bufferedWriterCloser() throws IOException {
final File file = folder.newFile(OUT_TXT);
final Closer closer = Closer.create();
try {
final Writer writer = closer.register(new BufferedWriter(new FileWriter(file)));
writer.write(HELLO_WORLD);
} finally {
closer.close();
}
}
@Test
public void bufferedWriterTryResources() throws IOException {
final File file = folder.newFile(OUT_TXT);
try (final Writer writer = new BufferedWriter(new FileWriter(file))) {
writer.write(HELLO_WORLD);
}
}
}
Here we have already 3 different ways using just the BufferedWriter
:
- bufferedWriterTryFinally: the
try... finally
pattern is used to make sure thewriter
is closed even when an exception during the write is thrown, - bufferedWriterCloser: using Guava's Closer, to close the writer in a finally clause,
- And bufferedWriterTryResources: Using
try with resources
syntax introduced in Java 7.
As you can see, depending on how you proceed to close the writer, the syntax differs slightly. But, overall the job done is the same.
Please note that the examples above all overwrite the file content. If you wish to append to the file instead:
@Test
public void bufferedWriterTryResourcesAppend() throws IOException {
final File file = folder.newFile(OUT_TXT);
try (final Writer writer = new BufferedWriter(new FileWriter(file, true))) {
writer.write(HELLO_WORLD);
}
}
See the true
2nd argument added to the FileWriter
? It's equivalent to append=true
.
The written file will contain the string Hello World!
.
PrintWriter¶
Now let's use the PrintWriter
which adds some nice features over the standard BufferedWriter
.
package com.octoperf;
import com.google.common.io.Closer;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
public class JavaWriteFileTest {
private static final String HELLO_WORLD = "Hello World!";
private static final String OUT_TXT = "out.txt";
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Test
public void printWriterTryFinally() throws IOException {
final File file = folder.newFile(OUT_TXT);
PrintWriter writer = null;
try {
writer = new PrintWriter(new FileWriter(file));
writer.printf("Hello %s!", "John Smith");
} finally {
if (writer != null) {
writer.close();
}
}
}
@Test
public void printWriterCloser() throws IOException {
final File file = folder.newFile(OUT_TXT);
final Closer closer = Closer.create();
try {
final PrintWriter writer = closer.register(new PrintWriter(new FileWriter(file)));
writer.printf("Hello %s!", "John Smith");
} finally {
closer.close();
}
}
@Test
public void printWriterTryResources() throws IOException {
final File file = folder.newFile(OUT_TXT);
try (PrintWriter writer = new PrintWriter(new FileWriter(file))){
writer.printf("Hello %s!", "John Smith");
}
}
}
Again, we used the 3 different closing methods to produce the same result.
writer.printf("Hello %s!", "John Smith");
is a convenient method to write a formatted string.
The written file will contain the string Hello John Smith!
.
RandomAccessFile¶
RandomAccessFile
is recommended if you need to write a file at a specific location.
package com.octoperf;
import com.google.common.io.Closer;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.io.Writer;
import static com.google.common.base.Charsets.UTF_8;
import static org.junit.Assert.assertEquals;
public class JavaWriteFileTest {
private static final String HELLO_WORLD = "Hello World!";
private static final String OUT_TXT = "out.txt";
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Test
public void randomAccessFile() throws IOException {
final File file = folder.newFile(OUT_TXT);
randomWrite(file, 2018, 4);
assertEquals(2018, randomRead(file, 4));
randomWrite(file, 2019, 4);
assertEquals(2019, randomRead(file, 4));
}
private static void randomWrite(
final File file,
final int data,
final long position) throws IOException {
try (final RandomAccessFile writer = new RandomAccessFile(file, "rw")) {
writer.seek(position);
writer.writeInt(data);
}
}
private static int randomRead(
final File file,
final long position)
throws IOException {
try (final RandomAccessFile reader = new RandomAccessFile(file, "r")) {
reader.seek(position);
return reader.readInt();
}
}
}
Let's describe how the code works:
randomWrite
method: it takes aFile
as input, and writes an integer at a given position,randomRead
method: it takes aFile
as input, and reads an integer at a given position.
The RandomAccessFile.seek(int position)
allows to set the file pointer to the specific offset, in bytes. In both cases, the content of the file is overwritten.
Closing Resources¶
When writing or reading content to or from a File, one must make sure to always close the associated resources. This is very important! Otherwise a pointer to the file stays open and can cause some strange behaviors:
- Reaching open file limit,
- Or being unable to write to a file (because it's already open).
To avoid these issues (which occurs in many strange ways, very difficult to debug), always make sure to close resources.
The first approach was introduced in JDK6:
InputStream in = null;
try {
in = openInputStream();
// do something with in
} finally {
if (in != null) {
in.close();
}
}
The stream is closed in a finally clause with a null check. It's pretty ugly sadly. Worse, think about the code if you need to copy content by reading one File and writing in another.
InputStream in = null;
try {
in = openInputStream();
OutputStream out = null;
try {
out = openOutputStream();
// do something with in and out
} finally {
if (out != null) {
out.close();
}
}
} finally {
if (in != null) {
in.close();
}
}
Now, you need to make sure to close both input and output streams... That's painful and error prone!
A cleaner solution has been introduced in JDK7+, using try resources
syntax:
try (InputStream in = openInputStream();
OutputStream out = openOutputStream()) {
// do stuff with in and out
}
This way, both the input and output streams are closed automatically. Nice, isn't it?
Another approach, is to use Guava's Closer. First add the dependency to your Maven POM:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
Replace ${guava.version}
by the latest version available. Then, you can use it right away in your code:
Closer closer = Closer.create();
try {
InputStream in = closer.register(openInputStream());
OutputStream out = closer.register(openOutputStream());
} catch (Throwable e) {
throw closer.rethrow(e);
} finally {
closer.close();
}
No matter which method you prefer, what matters is to always close streams and resources properly. Now we're ready to explore how to write Files!
FileChannel¶
As of JDK4, as new File Api has been introduced. It's called the Java NIO which stands for Java New IO. This API is quite difficult to use:
package com.octoperf;
import com.google.common.io.Closer;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import static com.google.common.base.Charsets.UTF_8;
import static org.junit.Assert.assertEquals;
public class JavaWriteFileTest {
private static final String HELLO_WORLD = "Hello World!";
private static final String OUT_TXT = "out.txt";
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Test
public void writeUsingFileChannel() throws IOException {
final File file = folder.newFile(OUT_TXT);
try (
final RandomAccessFile stream = new RandomAccessFile(file, "rw");
final FileChannel channel = stream.getChannel()
) {
final byte[] bytes = HELLO_WORLD.getBytes(UTF_8);
final ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
buffer.put(bytes);
buffer.flip();
channel.write(buffer);
}
try (final RandomAccessFile reader = new RandomAccessFile(file, "r")) {
assertEquals(HELLO_WORLD, reader.readLine());
}
}
}
Some low level libraries may use it, but otherwise it's not recommended to write content into files in Java this way. The code is way too verbose, and Java NIO.2 has been introduced since JDK7 to make writing files easier.
Java NIO 2¶
Here is an example using the Path
and Files
APIs introduced in Java 7:
package com.octoperf;
import com.google.common.io.Closer;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import static com.google.common.base.Charsets.UTF_8;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static org.junit.Assert.assertEquals;
public class JavaWriteFileTest {
private static final String HELLO_WORLD = "Hello World!";
private static final String OUT_TXT = "out.txt";
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Test
public void writeUsingNIO2() throws IOException {
final File file = folder.newFile(OUT_TXT);
try (final BufferedWriter writer = Files.newBufferedWriter(file.toPath(), UTF_8, TRUNCATE_EXISTING)) {
writer.write(HELLO_WORLD);
}
try (final BufferedReader reader = Files.newBufferedReader(file.toPath(), UTF_8)) {
assertEquals(HELLO_WORLD, reader.readLine());
}
}
}
In this example, the Hello World!
string is written using UTF-8 charset. Then we check the file contains the expected string. We have to use the bridge File.toPath
to convert the legacy File
created by the JUnit TemporaryFolder
into a Path
.
Write String Line by Line¶
To write multiple lines into a single file, you can use a BufferedWriter
as shown in the example below:
@Test
public void writeFileLineByLine() throws IOException {
final File file = folder.newFile(OUT_TXT);
try (
final BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(file));
final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(fos));
) {
for (int i = 0; i < 10; i++) {
writer.write(HELLO_WORLD);
writer.newLine();
}
}
}
In this case, we write the Hello World
string 10 times, one per line. The use of BufferedWriter
is mandatory because the newLine()
method is defined on it.
Apache Commons IO¶
Apache Commons IO provides some facilities to write to files in Java. To use commons-io
, please add the relevant dependency to your Maven pom.xml
:
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
Replace ${commons-io.version}
with the latest version available.
@Test
public void writeFileCommonsIO() throws IOException {
final File file = folder.newFile(OUT_TXT);
try (final FileOutputStream output = FileUtils.openOutputStream(file)) {
output.write(HELLO_WORLD.getBytes(UTF_8));
}
FileUtils.writeLines(file, ImmutableList.of(HELLO_WORLD.getBytes(UTF_8)));
}
As you can see, the FileUtils
method provides some convenient methods to open output streams on files, write several lines at once and more. You can explore the entire FileUtils API Here.
Conclusion¶
Don't forget these important things when dealing with files:
- Always properly close streams and resources to avoid open files limit / write issues,
- Make sure to read or write to files your application has the right to access,
- Make sure the file exists before writing to it.
Manipulating files can be more difficult than it sounds. Most of the methods and classes throw checked exceptions like IOException
, FileNotFoundException
and more. This sometimes clutters the code as you have to catch them and handle those exceptions properly.
Try to stick to the convention used by your development team: avoid using many different methods to read / write files, as this can be confusing. There is no method really better than any other. Use only those you are comfortable to work with.