Files.copy() and Files.move()
Copy and move files with explicit replace and atomic options, save an upload stream to disk, and know what copying a directory does.
public static Path copy(Path source, Path target, CopyOption... options) throws IOException- Added in Java Java 7
Parameters
| Parameter | Type | Description |
|---|---|---|
$source | Path | The file to copy. |
$target | Path | The path of the new file, including its file name. It is not a directory to copy into. |
$optionsoptional | CopyOption...= fail if target exists; follow links | StandardCopyOption.REPLACE_EXISTING, COPY_ATTRIBUTES, or LinkOption.NOFOLLOW_LINKS. |
Return value
PathThe target path.
Errors
FileAlreadyExistsException if the target exists and REPLACE_EXISTING is not given; DirectoryNotEmptyException if REPLACE_EXISTING is given but the target is a non-empty directory; NoSuchFileException if the source or the target's parent is missing; IOException for other failures. Files.move adds AtomicMoveNotSupportedException when ATOMIC_MOVE cannot be honoured.
Files.copy(source, target) copies one file and Files.move(source, target) moves or renames it. Both have been in java.nio.file.Files since Java 7, take the same CopyOption values and return the target.
The target is the new file, not a folder
Unlike the cp command, passing a directory as the target does not put the file inside it. Build the full target path: Files.copy(src, backups.resolve(src.getFileName())).
Existing targets
By default both methods throw FileAlreadyExistsException if the target exists. That is a safe default: nothing is overwritten by accident. Pass StandardCopyOption.REPLACE_EXISTING when overwriting is intended. COPY_ATTRIBUTES also copies the last-modified time and, where supported, permissions.
Moves and atomicity
Within one file system a move is a rename: fast, whatever the file size. Across file systems, for example from a container's /tmp to a mounted volume, it becomes a copy followed by a delete, and another process can see a partly written target during the copy. StandardCopyOption.ATOMIC_MOVE demands a true rename and throws AtomicMoveNotSupportedException if one is not possible, which is how you make sure readers never see half a file. Write to a temporary file in the destination directory, then move it with ATOMIC_MOVE.
Streams and directories
Two overloads work with streams: Files.copy(InputStream, Path, CopyOption...) saves an upload or download to disk and returns the number of bytes, and Files.copy(Path, OutputStream) sends a file to a response body. Neither closes the stream you pass.
Copying a directory creates an empty directory at the target; its contents are not copied. Moving a directory works when it is a rename on the same file system, but fails for a non-empty directory that would need its contents moved across file systems. To copy a tree, walk it and copy each entry.
Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING);
Files.move(tmp, live, StandardCopyOption.ATOMIC_MOVE);
long bytes = Files.copy(request.getInputStream(), uploadPath);Example
When to use it
- Save an uploaded file from the request input stream straight to disk without buffering it in memory.
- Publish a regenerated price feed or sitemap with an atomic move so readers never fetch a partial file.
- Move processed import files from an inbox directory to an archive directory after a batch job succeeds.
Frequently asked questions
How do I overwrite a file with Files.copy?
Pass StandardCopyOption.REPLACE_EXISTING: Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING). Without it, an existing target causes FileAlreadyExistsException.
How do I rename a file in Java?
Use Files.move(path, path.resolveSibling("new-name.txt")). On the same file system this is a rename and does not copy the data.
Does Files.copy copy a directory's contents?
No. Copying a directory creates an empty directory at the target. Walk the tree with Files.walk and copy each entry to recreate its contents.
More examples
Saving an upload stream to disk
Files.copy(InputStream, Path) streams the bytes to disk without holding the whole upload in memory, and returns the count for logging or quota checks. It does not close the input stream, so the try-with-resources block does.
void main() throws IOException {
Path dir = Files.createTempDirectory("uploads");
Path target = dir.resolve("avatar-42.png");
try {
byte[] body = new byte[2048]; // stands in for a request body
try (InputStream in = new ByteArrayInputStream(body)) {
long bytes = Files.copy(in, target);
IO.println("saved " + bytes + " bytes");
}
try (InputStream again = new ByteArrayInputStream(new byte[10])) {
Files.copy(again, target, StandardCopyOption.REPLACE_EXISTING);
}
IO.println("size after replace: " + Files.size(target));
} finally {
Files.deleteIfExists(target);
Files.deleteIfExists(dir);
}
}
// Output:
// saved 2048 bytes
// size after replace: 10Publishing a file with an atomic move
The temporary file lives in the same directory as the target, so the move is a single rename and readers see version 1 or version 2, never a partial file. On Linux an atomic rename replaces an existing target; the fallback branch handles file systems where atomic moves are not possible.
void main() throws IOException {
Path dir = Files.createTempDirectory("feed");
Path live = dir.resolve("prices.json");
try {
Files.writeString(live, "{\"version\": 1}");
Path tmp = Files.createTempFile(dir, "prices-", ".tmp");
Files.writeString(tmp, "{\"version\": 2}");
try {
Files.move(tmp, live, StandardCopyOption.ATOMIC_MOVE);
} catch (AtomicMoveNotSupportedException e) {
Files.move(tmp, live, StandardCopyOption.REPLACE_EXISTING);
}
IO.println(Files.readString(live));
try (Stream<Path> files = Files.list(dir)) {
IO.println("files: " + files.count());
}
} finally {
Files.deleteIfExists(live);
Files.deleteIfExists(dir);
}
}
// Output:
// {"version": 2}
// files: 1Copying a directory tree
Files.copy on a directory makes an empty directory and nothing more. Walking the source and copying each entry to the matching relative path copies the whole tree; the first entry is the source directory itself, which creates the target root.
void copyTree(Path source, Path target) throws IOException {
try (Stream<Path> tree = Files.walk(source)) {
for (Path p : tree.toList()) {
// walk lists each directory before its contents, so parents always exist first.
Files.copy(p, target.resolve(source.relativize(p).toString()));
}
}
}
void main() throws IOException {
Path root = Files.createTempDirectory("site");
Path src = root.resolve("templates");
try {
Files.createDirectories(src.resolve("email"));
Files.writeString(src.resolve("email/welcome.html"), "<p>Hi</p>");
Files.writeString(src.resolve("base.html"), "<html></html>");
Path shallow = Files.copy(src, root.resolve("shallow"));
try (Stream<Path> s = Files.list(shallow)) {
IO.println("plain copy entries: " + s.count());
}
Path deep = root.resolve("deep");
copyTree(src, deep);
try (Stream<Path> s = Files.walk(deep)) {
IO.println("tree copy: " + s.filter(Files::isRegularFile)
.map(p -> deep.relativize(p).toString()).sorted().toList());
}
} finally {
try (Stream<Path> tree = Files.walk(root)) {
for (Path p : tree.sorted(Comparator.reverseOrder()).toList()) {
Files.delete(p);
}
}
}
}
// Output:
// plain copy entries: 0
// tree copy: [base.html, email/welcome.html]
Discussion