Files.writeString()
Write text to a file in one call, control create, overwrite and append with open options, and avoid half-written files.
public static Path writeString(Path path, CharSequence csq, Charset cs, OpenOption... options) throws IOException- Added in Java Java 11
Parameters
| Parameter | Type | Description |
|---|---|---|
$path | Path | The file to write. |
$csq | CharSequence | The text to write, such as a String or StringBuilder. Written exactly as given; no line separator is added. |
$csoptional | Charset= StandardCharsets.UTF_8 | The charset used to encode the text. Leave it out to use writeString(Path, CharSequence, OpenOption...), which encodes as UTF-8. |
$optionsoptional | OpenOption...= CREATE, TRUNCATE_EXISTING, WRITE | How the file is opened, for example StandardOpenOption.APPEND or CREATE_NEW. With no options the method behaves as if CREATE, TRUNCATE_EXISTING and WRITE were given. |
Return value
PathThe path that was written, so calls can be chained.
Errors
IOException if the file cannot be created or written (NoSuchFileException when the parent directory is missing, FileAlreadyExistsException with CREATE_NEW), or when the text cannot be encoded in the charset (UnmappableCharacterException); IllegalArgumentException for an invalid combination of options, such as APPEND with TRUNCATE_EXISTING or READ; UnsupportedOperationException for an unsupported option.
Files.writeString(path, text) encodes the text as UTF-8, writes it and closes the file. With no options it creates the file if needed and truncates it if it already exists, so the call replaces the old contents completely.
Open options change the behaviour
Files.writeString(p, text); // create or replace
Files.writeString(p, text, StandardOpenOption.APPEND); // add to the end; file must exist
Files.writeString(p, text, StandardOpenOption.CREATE,
StandardOpenOption.APPEND); // create if missing, then append
Files.writeString(p, text, StandardOpenOption.CREATE_NEW); // fail if the file existsOnce you pass any option, the defaults no longer apply. APPEND on its own throws NoSuchFileException for a missing file, which surprises people who expected it to create one. CREATE_NEW is the race-free way to claim a file name: the check and the creation happen as one operation, so two requests cannot both win.
No line separators are added
The method writes exactly the characters you pass. If each call should produce a line, add "\n" yourself. Files.write(path, lines) is the line-oriented sibling and adds a separator after every line.
Half-written files
Truncate-then-write means a reader can see an empty or partial file while a write is in progress, and a crash can leave it that way. For files other processes read (a cache, a generated config, a feed), write to a temporary file in the same directory and then Files.move it over the target with ATOMIC_MOVE. Readers then see either the old file or the new one. Files.createTempFile makes the file owner-only (rw-------) on Linux, and the move keeps those permissions, so set them before the move if a process running as another user must read the result.
The parent directory must exist; call Files.createDirectories(path.getParent()) first when it might not. For large or streaming output, use Files.newBufferedWriter rather than building one huge String in memory.
Example
When to use it
- Write a generated nginx or service config file that another process reloads, using a temporary file and an atomic move.
- Save a nightly CSV export of orders to a reports directory for a finance team to collect.
- Claim a lock or marker file with CREATE_NEW so only one copy of a scheduled job runs at a time on a host.
Frequently asked questions
How do I append to a file instead of overwriting it in Java?
Pass open options: Files.writeString(path, text, StandardOpenOption.CREATE, StandardOpenOption.APPEND). Without CREATE, APPEND throws NoSuchFileException when the file does not exist.
Does Files.writeString add a newline?
No. It writes exactly the characters you pass. Add "\n" yourself, or use Files.write(path, listOfLines), which adds the platform line separator after every line.
Why does Files.writeString throw NoSuchFileException?
Either the parent directory does not exist, or you passed APPEND without CREATE for a file that is not there yet. Call Files.createDirectories(path.getParent()) first, or add StandardOpenOption.CREATE.
More examples
Replacing a file atomically
A reader polling feature-flags.json never sees an empty or partial file, because the rename swaps the whole file in one step. The finally block removes the temporary file if anything failed before the move. The temporary file is created owner-only on Linux, so the published file is too unless you change its permissions first.
void writeAtomically(Path target, String content) throws IOException {
// The temporary file must be in the same directory (same file system) for an atomic move.
Path tmp = Files.createTempFile(target.getParent(), "write-", ".tmp");
try {
Files.writeString(tmp, content);
Files.move(tmp, target, StandardCopyOption.ATOMIC_MOVE);
} finally {
Files.deleteIfExists(tmp);
}
}
void main() throws IOException {
Path dir = Files.createTempDirectory("flags");
Path flags = dir.resolve("feature-flags.json");
try {
writeAtomically(flags, "{\"newCheckout\": false}");
writeAtomically(flags, "{\"newCheckout\": true}");
IO.println(Files.readString(flags));
try (Stream<Path> files = Files.list(dir)) {
IO.println("files in dir: " + files.count());
}
} finally {
Files.deleteIfExists(flags);
Files.deleteIfExists(dir);
}
}
// Output:
// {"newCheckout": true}
// files in dir: 1Writing a CSV export
writeString accepts any CharSequence, so a StringBuilder can be written without calling toString. The explicit '\n' gives the same line endings on every operating system, which matters when another service parses the file.
record Order(int id, String customer, long totalPence) {}
void main() throws IOException {
List<Order> orders = List.of(
new Order(1001, "Ada", 2599),
new Order(1002, "Linus", 1200));
StringBuilder csv = new StringBuilder("id,customer,total_pence\n");
for (Order o : orders) {
csv.append(o.id()).append(',').append(o.customer()).append(',').append(o.totalPence()).append('\n');
}
Path file = Files.createTempFile("orders", ".csv");
try {
Files.writeString(file, csv);
List<String> lines = Files.readAllLines(file);
IO.println(lines.size() + " lines, " + Files.size(file) + " bytes");
IO.println(lines.get(1));
} finally {
Files.deleteIfExists(file);
}
}
// Output:
// 3 lines, 54 bytes
// 1001,Ada,2599Text the charset cannot encode
writeString refuses to replace characters it cannot encode, and in the JDK the text is encoded before the file is opened, so nothing is written. With the default UTF-8 the two accented letters take two bytes each, which is why the byte count and the character count differ.
void main() throws IOException {
Path dir = Files.createTempDirectory("enc");
Path file = dir.resolve("name.txt");
String name = "Zo\u00EB Fran\u00E7ois";
try {
try {
Files.writeString(file, name, StandardCharsets.US_ASCII);
} catch (UnmappableCharacterException e) {
IO.println("cannot encode: " + e.getMessage());
}
IO.println("file created: " + Files.exists(file));
Files.writeString(file, name);
IO.println("UTF-8 bytes: " + Files.size(file) + " for " + name.length() + " chars");
} finally {
Files.deleteIfExists(file);
Files.deleteIfExists(dir);
}
}
// Output:
// cannot encode: Input length = 1
// file created: false
// UTF-8 bytes: 14 for 12 chars
Discussion