Back to Blog
Java

Java FileWriter: Writing Text Files with Character Encoding

java filewriter: Learn how to use Java FileWriter for writing text files, handle encoding, manage resources, and choose between FileWriter and BufferedWriter.

JavaFileWriterI/OCharacter EncodingBufferedWriter
Java FileWriter writing text to a file with a character encoding symbol in the background.

The java filewriter class, java.io.FileWriter, is the simplest way to write character data to a file in Java. It writes text directly to a file using the platform's default character encoding unless you specify otherwise. This makes it convenient for quick scripts and small applications, but its simplicity comes with limitations that matter in production code.

What FileWriter Does

FileWriter is a character-oriented output stream that writes text to a file. It extends OutputStreamWriter and provides constructors that take a file path, a File object, or a FileDescriptor. The class handles the conversion of characters to bytes using an internal CharsetEncoder. By default, it uses the platform's default charset, which is often UTF-8 on modern systems but can vary, especially on legacy systems.

Here is the most basic usage:

import java.io.FileWriter; import java.io.IOException; public class WriteExample { public static void main(String[] args) { try (FileWriter writer = new FileWriter("output.txt")) { writer.write("Hello, world!"); } catch (IOException e) { e.printStackTrace(); } } }

This creates a new file named output.txt in the current working directory, or truncates it if it already exists. The try-with-resources statement automatically closes the writer, which is essential because FileWriter holds a file descriptor that must be released.

Appending vs. Overwriting

By default, FileWriter overwrites an existing file. To append to the end of a file, use the two-argument constructor that accepts a boolean append flag:

FileWriter writer = new FileWriter("log.txt", true);

When append is true, the writer opens the file in append mode and positions the cursor at the end. This is useful for logging and accumulating data across multiple runs. The distinction matters because overwriting is destructive; if you need to preserve previous content, you must explicitly enable append.

Character Encoding and FileWriter

FileWriter uses the platform's default charset unless you specify one. Since Java 11, you can pass a Charset to the constructor:

FileWriter writer = new FileWriter("data.txt", Charset.forName("UTF-8"));

Before Java 11, FileWriter did not expose a charset parameter, forcing you to use OutputStreamWriter with a FileOutputStream to control encoding. This was a common source of bugs when writing files that would be read by other systems expecting a specific encoding. If you need consistent encoding across environments, always specify the charset explicitly. Relying on the default can lead to corrupted text when the platform default differs from what the consumer expects.

Closing Resources and try-with-resources

FileWriter implements Closeable, so it must be closed after use. Failing to close a writer can leak file descriptors and leave data in an internal buffer unwritten. The try-with-resources statement is the recommended pattern because it guarantees closure even when an exception occurs:

try (FileWriter writer = new FileWriter("output.txt")) { writer.write("data"); } catch (IOException e) { // handle error }

If you use a plain try block, you must close the writer in a finally block. This is more verbose and error-prone, especially when multiple resources are involved. The try-with-resources approach also handles the case where write throws an exception, ensuring the writer is still closed.

Error Handling

FileWriter methods throw IOException when the file cannot be opened, written, or closed. Common causes include missing directories, permission issues, or a full disk. You should catch these exceptions and respond appropriately. In a production application, logging the error and possibly retrying with a fallback path is typical. Avoid swallowing exceptions silently, as that makes debugging difficult.

One subtle issue is that FileWriter does not buffer output. Each write call passes data directly to the underlying stream, which can be inefficient when writing many small pieces of text. This leads to a performance concern that we address next.

Performance Considerations

Because FileWriter writes directly to the underlying file stream, each call to write can trigger a system call. For small files or infrequent writes, this is acceptable. But when writing large amounts of text or performing many small writes, the overhead can become noticeable. Wrapping FileWriter in a BufferedWriter reduces the number of system calls by buffering data in memory and flushing to disk in larger chunks:

try (BufferedWriter writer = new BufferedWriter(new FileWriter("large.txt"))) { writer.write("line 1"); writer.write("line 2"); }

BufferedWriter also provides a newLine() method that writes the platform-specific line separator, which is more portable than hardcoding \n. The performance gain is most significant when writing many small pieces of data. However, BufferedWriter adds memory overhead for the buffer, so for tiny files the difference is negligible.

When to Use BufferedWriter

Choosing between FileWriter and BufferedWriter depends on the write pattern. If you write a few large chunks of text, FileWriter alone is sufficient. If you write many small pieces or need to write line by line, BufferedWriter is better. The following table summarizes the tradeoffs:

CriterionFileWriterBufferedWriter with FileWriter
Write patternLarge, infrequent writesMany small writes or line-oriented
System callsOne per writeBuffered, fewer system calls
Memory overheadMinimalAdditional buffer (default 8192 chars)
ConvenienceSimple constructorAdds newLine() method

For most file-writing tasks in production, wrapping FileWriter in a BufferedWriter is a safe default because it improves performance without complicating the code.

Alternatives to FileWriter

FileWriter is a low-level class. For more advanced needs, consider Files.newBufferedWriter from the java.nio.file package. It offers the same buffering behavior but with more control over file options, such as creating, appending, or truncating, and it supports Charset directly:

import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; Path path = Path.of("output.txt"); try (var writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND)) { writer.write("appended text"); }

This approach is more verbose but aligns with the modern NIO API and avoids the historical charset limitation of FileWriter. For simple cases, FileWriter remains a valid choice, but for new code, Files.newBufferedWriter is often preferred because it is more explicit and integrates with the Path API.

Handling File Paths and Directories

FileWriter does not create missing directories. If the target file is in a directory that does not exist, the constructor throws a FileNotFoundException. You must create the directory beforehand using Files.createDirectories:

Path dir = Path.of("logs"); Files.createDirectories(dir); try (FileWriter writer = new FileWriter(dir.resolve("app.log").toFile())) { writer.write("log entry"); }

This is a common pitfall when writing to nested paths. Always ensure the parent directory exists before opening the writer. Alternatively, use Files.newBufferedWriter with StandardOpenOption.CREATE which still requires the directory to exist.

Character Encoding Pitfalls

Even with an explicit charset, FileWriter can still produce unexpected results if the Charset does not support all characters being written. For example, writing characters outside the charset's range will replace them with the replacement character ? or throw an exception depending on the CodingErrorAction. The default behavior is to replace malformed input. If you need strict validation, use OutputStreamWriter with a CharsetEncoder configured to report errors. This is rarely necessary for typical text files but matters when data integrity is critical.

Another pitfall is mixing FileWriter with PrintWriter or other writers that apply their own encoding. PrintWriter can wrap a FileWriter, but it inherits the charset from the underlying writer. If you need to print formatted text with a specific encoding, construct the PrintWriter with a FileWriter that already has the desired charset.

Final Code Example: A Robust Writer Utility

Combining the concepts above, here is a utility method that writes text to a file with a specified charset, creates parent directories, and uses buffering:

import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; public class TextFileWriter { public static void write(String content, Path path, Charset charset) throws IOException { Path parent = path.getParent(); if (parent != null) { Files.createDirectories(parent); } try (BufferedWriter writer = new BufferedWriter( new FileWriter(path.toFile(), charset))) { writer.write(content); } } }

This method ensures the directory exists, uses an explicit charset, and buffers the output. It is suitable for many production scenarios where you need to write a complete string to a file. For append mode, you would add a boolean parameter and pass it to the FileWriter constructor. This utility demonstrates how to use java filewriter effectively while avoiding common pitfalls.

java filewriter: Practical Usage and Code Examples | RYUSLOG DEV