W3docs

Creating Files in Java

Create new files and directories in Java with File.createNewFile, Files.createFile, and Files.createDirectories.

Creating Files in Java

"Make a new empty file" and "make this directory tree" are two of the smallest operations the file system offers, yet Java exposes four overlapping ways to do each. The differences matter — they're the difference between "fails if the file exists" and "overwrites silently," between "mkdir" and "mkdir -p," between a boolean-returning legacy method and an exception-throwing modern one.

This chapter covers the four creators:

  • File.createNewFile() — legacy file creation.
  • File.mkdir() / File.mkdirs() — legacy directory creation.
  • Files.createFile(path) — modern atomic "create or fail."
  • Files.createDirectory(path) / Files.createDirectories(path) — modern directory creation.

Plus the temp-file helpers (Files.createTempFile, Files.createTempDirectory) and the OpenOption flags that let writers create files implicitly.

File.createNewFile() — legacy, returns a boolean

File f = new File("data/users.txt");
boolean created = f.createNewFile();      // true if it actually created the file
                                          // false if it already existed
                                          // throws IOException if the parent dir is missing

The contract is atomic check-and-create: the OS guarantees no other process can create the same path between the existence check and the creation. That makes createNewFile a primitive lock under some legacy "PID file" idioms — if (!f.createNewFile()) throw new IllegalStateException("already running");

What it does not do:

  • It does not create missing parent directories. new File("does/not/exist/file.txt").createNewFile() throws IOException.
  • It returns false (not throws) when the file already exists.

If you only need the file to exist by the end of the call, the false return value is fine. If you need the file to be brand-new (a lock semantics), false is your trigger to take a different path.

File.mkdir() and File.mkdirs()

new File("logs").mkdir();           // creates "logs" — fails if "." has no perms or parent missing
new File("a/b/c").mkdirs();          // creates "a", "a/b", and "a/b/c" — like `mkdir -p`

Both return boolean, both lose information about why a failure happened. mkdir fails if a parent is missing; mkdirs doesn't. Both succeed (return true) only if the directory was newly created — if it already exists, they return false. Combined with the "no error info" problem, this is the kind of legacy API that gets wrapped in a helper:

File dir = new File("data");
if (!dir.exists() && !dir.mkdirs()) throw new IOException("cannot create " + dir);

The modern Files.createDirectories(path) is the one-liner replacement.

Files.createFile(path) — modern, throws

Files.createFile is the java.nio.file peer of File.createNewFile() with one important difference: it throws instead of returning a boolean.

Path p = Path.of("data/users.txt");
Files.createFile(p);                          // creates an empty regular file
                                              // throws FileAlreadyExistsException if it exists
                                              // throws NoSuchFileException if the parent is missing

FileAlreadyExistsException is what you catch if the existence outcome matters; NoSuchFileException is what you catch (or prevent with createDirectories) if the parent might not be there. The exception types are specific subclasses of IOException, so a blanket catch (IOException e) still works.

You can pass FileAttribute arguments to set POSIX permissions at creation time on Unix — the most common use is to ensure secret files (private keys, tokens) are created 0600:

import static java.nio.file.attribute.PosixFilePermissions.*;
var attr = asFileAttribute(fromString("rw-------"));
Files.createFile(Path.of("/tmp/secret"), attr);   // born with 0600 permissions, atomically

(That call throws UnsupportedOperationException on Windows, which has no POSIX permission model — guard with a platform check if you target both.)

Files.createDirectory versus Files.createDirectories

The same difference as mkdir versus mkdir -p, just exception-based:

Files.createDirectory(Path.of("logs"));          // one level deep; parent must exist
Files.createDirectories(Path.of("a/b/c"));        // creates every missing ancestor

createDirectory throws FileAlreadyExistsException if the target already exists and is not a directory; if it's already a directory, it throws too (not what you usually want).

createDirectories is the friendlier choice: it does nothing if every directory is already there, and creates whatever's missing otherwise. It does not throw if the path already exists as a directory. That makes it idempotent — safe to call at startup without an exists() check.

Temp files and directories

For tests, scratch space, and "I need somewhere safe to put this for a few minutes," the JDK ships Files.createTempFile and Files.createTempDirectory:

Path scratch = Files.createTempFile("session-", ".log");          // /tmp/session-3829387.log
Path workdir = Files.createTempDirectory("export-");               // /tmp/export-1827392

Both pick a unique name in the system temp directory, both return a Path to the new entry, and both create the entry with restrictive permissions on Unix. The prefix and suffix are hints the JDK appends a unique value to — you don't get to choose the exact name (that's the point: another caller can't predict it and clobber yours).

Temp files are not deleted automatically. You either:

  • Call Files.deleteIfExists(path) when you're done; or
  • Call path.toFile().deleteOnExit() to schedule a delete on JVM shutdown (cleared by hard kills); or
  • Open the file with StandardOpenOption.DELETE_ON_CLOSE if you only need it while a stream is open.

Writers create files implicitly

Most of the time you don't need a "create file" call at all — a writer creates one for you. Files.newBufferedWriter, Files.write, and Files.writeString all accept OpenOption... varargs that decide what happens when the file does or doesn't exist:

import static java.nio.file.StandardOpenOption.*;
Files.writeString(path, "hello\n", CREATE, WRITE, TRUNCATE_EXISTING);
Files.writeString(path, "more\n",  CREATE, WRITE, APPEND);
Files.writeString(path, "new\n",   CREATE_NEW);     // fails if file exists
  • CREATE — create if missing, otherwise open existing.
  • CREATE_NEW — create, throw FileAlreadyExistsException if it exists. The same semantics as Files.createFile.
  • TRUNCATE_EXISTING — clear the file's contents on open (the default for writeString when not appending).
  • APPEND — write at the end without truncating.

The default for Files.writeString (no options) is CREATE, WRITE, TRUNCATE_EXISTING — i.e. "create or overwrite." Files.newBufferedWriter defaults the same way. If you want append semantics, you must say so.

A worked example: each creator side by side

The program below builds a small tree from scratch under the system temp directory using both APIs and several open options. Each step prints what changed; the last block shows what happens when you re-run an operation against a path that already exists.

java— editable, runs on the server

What to take from the run:

  • The first legacy.createNewFile() returned true (created); the second returned false (already existed). The boolean doesn't tell you what happened — you have to remember the convention.
  • deep.mkdirs() succeeded once and returned false the second time. That false looks identical to "permission denied" or "parent missing" — exactly the missing-error-info problem Files solves.
  • Files.createFile on an existing path threw FileAlreadyExistsException. The exception type is specific, so a real handler can distinguish "already there" from "permission denied" without parsing strings.
  • Files.createDirectories called twice in a row did nothing harmful the second time. That's the property that makes it the right choice in startup code: no guard, just call it.
  • Files.writeString(log, "line 1\n") created the file because CREATE is in the default options. The second and third calls used APPEND explicitly, and the file accumulated three lines. The fourth call used CREATE_NEW and refused to overwrite. The defaults are designed for the "overwrite with new content" case; you opt into append.
  • Files.createTempFile(root, "scratch-", ".tmp") produced a name like scratch-1827392.tmp — your prefix and suffix, plus a unique chunk the JVM picks so two concurrent calls never collide.
  • The cleanup walks root in reverse order so child files and directories disappear before their parents. Files.delete refuses to delete a non-empty directory; that ordering is how a manual rm -rf is built.

What's next

You can create files; the next chapter, Reading Files in Java, reads them — first with the modern one-liners (Files.readString, Files.readAllLines, Files.lines), then with the classic FileReader / BufferedReader / Scanner decorator stack so the older code in the next chapters has a foundation.

Practice

Practice

You want a one-liner that creates a directory plus any missing parent directories, and does **nothing** if the directory already exists. Which call is it?