SwadhyAI Notes

All Java notes

Java Create Files

›Short versionOpen if you only have a minute.
  • Naming a Path leaves the disk alone. Files.createFile is the call that creates an empty file.
  • The parent directory must already exist. Files.createDirectories creates missing parents and succeeds when they are already there.
  • createFile is exclusive. If the name exists, it throws FileAlreadyExistsException. createNewFile returns false for that same case.
  • A user-supplied name is one path element, resolved under a root you chose. Reject an absolute input and any name that normalizes outside that root.
  • A temporary file belongs in the target’s directory when a later atomic move will publish it under the real name.

Creating a file is the moment the name becomes an empty entry on disk. does that for one path. The bytes come later, on the write page.

Parents first, then the file

notes/today.txt needs a named notes. This creates that directory chain, then the empty file.

CreateNote.java
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;

public class CreateNote {
    public static void main(String[] args) {
        Path file = Path.of("notes", "today.txt");

        try {
            Files.createDirectories(file.getParent());
            Files.createFile(file);
            System.out.println("Created: " + file.toAbsolutePath());
        } catch (FileAlreadyExistsException e) {
            System.out.println("Already there: " + file.toAbsolutePath());
        } catch (IOException e) {
            System.out.println("Could not create " + file.toAbsolutePath());
            e.printStackTrace();
        }
    }
}
Terminal
$ java CreateNote
Created: /Users/me/app/notes/today.txt
$ ls notes
today.txt

The second run hits the branch and prints Already there. The empty file from the first run is still in notes. createDirectories on that second run finds notes and returns.

File.createNewFile() is the same with a boolean. true means this call created the file. false means the name was already an entry. A missing parent is an IOException, so false is a narrower fact than “the call failed.”

createNewFile returned false. What is already on disk at that path?

An entry with that name. It may be a file, or it may be a directory. false means the exclusive create lost. It does not say you can open the entry for writing, and a check you made earlier can be stale if another process created the name in between.

A name under a root you chose

When the file name comes from a request, build the path from a root you control and a single relative name. An absolute string such as /etc/passwd, or a name with a separator, resolves outside that root if you skip the checks. The new file has no real path yet, so toRealPath() runs on the root after createDirectories has made sure that root exists.

CreateUnderRoot.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

static Path createUnderRoot(Path root, String fileName) throws IOException {
    Path leaf = Path.of(fileName);
    if (leaf.isAbsolute() || leaf.getNameCount() != 1 || fileName.indexOf('\0') >= 0) {
        throw new IllegalArgumentException("path escapes the root");
    }

    Path base = root.toAbsolutePath().normalize();
    Files.createDirectories(base);
    Path realRoot = base.toRealPath();
    Path candidate = realRoot.resolve(leaf).normalize();
    if (!candidate.startsWith(realRoot) || candidate.equals(realRoot)) {
        throw new IllegalArgumentException("path escapes the root");
    }
    return Files.createFile(candidate);
}

getNameCount() != 1 rejects any name that contains a separator, such as notes/today.txt or ../today.txt. The names . and .. are each one element, so they pass that test. normalize then turns them into the root or its parent, and equals or startsWith rejects them. createDirectories runs first so the root exists. toRealPath() follows a symlink at that root, and the new file is created in the directory the link points at.

Allowing several segments is a different method. Resolve the user string, normalize, and accept it when it startsWith the real root and is longer than the root. Create each parent with createDirectories only after that check. The overview page walks through why normalize has to happen before startsWith.

A file you intend to rename

A is how you get an exclusive empty file without choosing a public name. Put it beside the file you will replace.

TempBeside.java
Path target = Path.of("notes", "today.txt");
Files.createDirectories(target.getParent());
Path temp = Files.createTempFile(target.getParent(), ".part-", ".tmp");

The prefix .part- is long enough for createTempFile. The file is empty and already exclusive. Writing the bytes into this path, then moving it onto today.txt, is the subject of the write page: a crash during the write leaves the part file, and the name readers use stays intact.

Java Write Files picks up from that part file, with a charset and an atomic move.