To copy a file in Java, we call Files.copy(source, target), which writes every byte of the source file to a new target file and throws FileAlreadyExistsException if the target already exists. Passing StandardCopyOption.REPLACE_EXISTING overwrites the target instead, and COPY_ATTRIBUTES keeps the timestamps.
We copy files to back up a document before we change it, to publish a generated report into a shared folder, or to start a new file from a template. The same Files.copy() method also saves an InputStream, such as an uploaded file, to disk.
The following example copies an invoice to a second file, reads the copy back and overwrites it on the second call.
Path dir = Files.createTempDirectory("invoices");
Path source = Files.writeString(dir.resolve("invoice.txt"), "Total 120");
Path target = dir.resolve("invoice-copy.txt");
String name = Files.copy(source, target).getFileName().toString(); // "invoice-copy.txt"
String text = Files.readString(target); // "Total 120"
Path again = Files.copy(source, target); // FileAlreadyExistsException
Path replaced = Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); // overwrites the copy
boolean same = Files.mismatch(source, replaced) == -1; // true
Notice that the second plain copy() call fails, because Files.copy() never overwrites a file unless we ask for it. We start with the copy options and move on to streams, FileChannel and the older java.io classes, a copy that other programs never see half-written, and the library helpers.
1. Copying a File with Files.copy()
The Files.copy(Path, Path, CopyOption…) method belongs to java.nio.file.Files and works on Path objects. By default, it copies only the content, it fails when the target exists, and it follows a symbolic link and copies the file the link points to. The method returns the target path, so we can chain a call on the result.

The three standard options change that default behavior. We pass any combination of them as the last arguments.
| Option | Effect | Default without it |
|---|---|---|
| StandardCopyOption.REPLACE_EXISTING | Replaces an existing target file or empty directory | Throws FileAlreadyExistsException |
| StandardCopyOption.COPY_ATTRIBUTES | Copies file attributes, at least the last-modified time | The copy gets the current time |
| LinkOption.NOFOLLOW_LINKS | Copies a symbolic link as a link | Copies the file the link points to |
1.1. Overwriting an Existing File
A nightly job that refreshes latest-report.csv in a shared folder must replace yesterday’s file. Without REPLACE_EXISTING, the second run of that job stops with FileAlreadyExistsException. With the option, Java deletes the old target and writes the new content.
Path reports = Files.createTempDirectory("reports");
Path today = Files.writeString(reports.resolve("report-10-10.csv"), "day,total\n10,42");
Path latest = Files.writeString(reports.resolve("latest-report.csv"), "old");
Files.copy(today, latest, StandardCopyOption.REPLACE_EXISTING);
String content = Files.readString(latest); // "day,total\n10,42"
The option never replaces a directory that still has entries. If the target is a non-empty directory, copy() throws DirectoryNotEmptyException even with REPLACE_EXISTING.
1.2. Keeping the Timestamps with COPY_ATTRIBUTES
A plain copy is a new file, so its last-modified time is the moment of the copy. Backup tools and sync jobs compare timestamps to decide what changed, so they pass COPY_ATTRIBUTES. Which other attributes travel with it, such as POSIX permissions or the DOS hidden flag, depends on the operating system and the file system.
Path docs = Files.createTempDirectory("docs");
Path original = Files.writeString(docs.resolve("notes.txt"), "draft");
Files.setLastModifiedTime(original, FileTime.from(Instant.parse("2026-01-15T10:00:00Z")));
Path plain = Files.copy(original, docs.resolve("plain.txt"));
Path kept = Files.copy(original, docs.resolve("kept.txt"), StandardCopyOption.COPY_ATTRIBUTES);
FileTime plainTime = Files.getLastModifiedTime(plain); // time of the copy
FileTime keptTime = Files.getLastModifiedTime(kept); // 2026-01-15T10:00:00Z
1.3. Copying a File into Another Directory
The target argument is the full path of the new file, not the folder it goes into. To copy a file into a directory under the same name, we resolve the source file name against the directory. If the directory does not exist, the copy throws NoSuchFileException, so we create the directories first.
Path inbox = Files.createTempDirectory("inbox");
Path invoice = Files.writeString(inbox.resolve("invoice-7.txt"), "Total 80");
Path archive = inbox.resolveSibling(inbox.getFileName() + "-archive").resolve("2026");
Path missing = Files.copy(invoice, archive.resolve(invoice.getFileName())); // NoSuchFileException
Files.createDirectories(archive);
Path stored = Files.copy(invoice, archive.resolve(invoice.getFileName()));
boolean exists = Files.exists(stored); // true
Passing the directory itself as the target is a common mistake. If the directory exists, copy() throws FileAlreadyExistsException, and with REPLACE_EXISTING it tries to replace the directory with the file. Copying a whole folder needs a walk over its entries, which the copy a directory recursively guide covers.
2. Copying an InputStream to a File and a File to an OutputStream
Two more overloads of Files.copy() work with streams. The method copy(InputStream, Path, CopyOption…) writes everything from a stream to a file, and copy(Path, OutputStream) writes a file to any output stream. Neither of them closes the stream, so we open it in a try-with-resources block.
A web app that receives an uploaded profile picture gets an InputStream from the servlet or Spring MultipartFile. Saving it is one call, and REPLACE_EXISTING lets the user upload a new picture later. The stream overload accepts only REPLACE_EXISTING as an option.
Path uploads = Files.createTempDirectory("uploads");
byte[] picture = {1, 2, 3, 4, 5};
long saved;
try (InputStream in = new ByteArrayInputStream(picture)) {
saved = Files.copy(in, uploads.resolve("avatar.png"), StandardCopyOption.REPLACE_EXISTING);
}
long bytes = saved; // 5
The reverse direction sends a file to an OutputStream, such as an HTTP response or a zip entry. Since Java 9, InputStream.transferTo(OutputStream) copies between any two streams, which also helps when the source is not a file, as the InputStream to OutputStream article shows.
Path page = Files.writeString(Files.createTempFile("page", ".html"), "<h1>Hi</h1>");
ByteArrayOutputStream response = new ByteArrayOutputStream();
long sent = Files.copy(page, response); // 11
String body = response.toString(StandardCharsets.UTF_8); // "<h1>Hi</h1>"
A file on the classpath works the same way. We open it with getResourceAsStream() and pass the stream to Files.copy(), which is how an app writes a default configuration file to disk on its first start. The resources folder guide explains the resource paths.
3. Copying with FileChannel.transferTo()
A FileChannel can copy bytes to another channel with transferTo(). The JDK may hand the transfer to the operating system, so the bytes do not pass through a buffer in our Java code. The method is useful when we copy only a range of a file, or when the target is a socket channel rather than a file.
The method returns the number of bytes it moved, and that can be fewer than we asked for. A correct copy calls it in a loop until the whole file is transferred. Both channels are closed by try-with-resources.
static long copyWithChannels(Path source, Path target) throws IOException {
try (FileChannel in = FileChannel.open(source, StandardOpenOption.READ);
FileChannel out = FileChannel.open(target, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
long size = in.size();
long position = 0;
while (position < size) {
position += in.transferTo(position, size - position, out);
}
return position;
}
}
Path logs = Files.createTempDirectory("logs");
Path log = Files.writeString(logs.resolve("app.log"), "started\nstopped\n");
long moved = copyWithChannels(log, logs.resolve("app-copy.log")); // 16
For a whole-file copy, Files.copy() is the simpler choice and the JDK picks an efficient native copy for it. We use channels when we need the range or the socket target. The transferring data between channels article goes deeper into transferFrom() and transferTo().
4. Copying with FileInputStream and FileOutputStream
Older code copies files with FileInputStream and FileOutputStream and a hand-written byte loop. We still meet these classes when a library hands us a java.io.File, but the loop is no longer needed, because transferTo() does the buffered copy for us.
File sourceFile = Files.writeString(Files.createTempFile("menu", ".txt"), "soup, salad").toFile();
File targetFile = new File(sourceFile.getParentFile(), "menu-copy-" + sourceFile.getName());
long copied;
try (InputStream in = new FileInputStream(sourceFile);
OutputStream out = new FileOutputStream(targetFile)) {
copied = in.transferTo(out);
}
long total = copied; // 11
Unlike Files.copy(), a FileOutputStream overwrites an existing target without any warning. When we hold a File, calling file.toPath() and using Files.copy() gives us the safer default and clear exceptions in one line.
5. Making the Copy Safe for Other Readers
Copying a file is not an atomic operation, so a crash or a full disk can leave a half-written target behind. Another program that reads the target during the copy can also see a partial file. That matters for files other processes pick up, such as a configuration file a server reloads or a file dropped into a folder a batch job watches.
The fix is to copy into a temporary file in the same directory and rename it over the target with ATOMIC_MOVE. A rename on the same file system is atomic, so readers see either the old file or the complete new one. The rename or move a file article explains when ATOMIC_MOVE works.
static void copyAtomically(Path source, Path target) throws IOException {
Path temp = Files.createTempFile(target.getParent(), "copy-", ".tmp");
try {
Files.copy(source, temp, StandardCopyOption.REPLACE_EXISTING);
Files.move(temp, target, StandardCopyOption.ATOMIC_MOVE);
} finally {
Files.deleteIfExists(temp);
}
}
Path config = Files.createTempDirectory("config");
Path draft = Files.writeString(config.resolve("app.draft"), "port=8080");
Path live = Files.writeString(config.resolve("app.properties"), "port=80");
copyAtomically(draft, live);
String active = Files.readString(live); // "port=8080"
On Linux, macOS and Windows, the JDK replaces an existing target during an ATOMIC_MOVE, although the Files.move() contract leaves that choice to the implementation. If the temporary file and the target were on different file systems, the move would throw AtomicMoveNotSupportedException, which is why the temporary file goes into the target’s own directory.
6. Commons IO and Guava Options
Apache Commons IO and Guava both have one-line copy helpers. They are fine when the project already depends on the library, but neither adds anything for a single file that Files.copy() lacks.
File src = Files.writeString(Files.createTempFile("price", ".txt"), "9.99").toFile();
File commonsCopy = new File(src.getParentFile(), "commons-" + src.getName());
File guavaCopy = new File(src.getParentFile(), "guava-" + src.getName());
FileUtils.copyFile(src, commonsCopy);
com.google.common.io.Files.copy(src, guavaCopy);
String both = Files.readString(commonsCopy.toPath()) + Files.readString(guavaCopy.toPath()); // "9.999.99"
Two defaults differ from the JDK. The method FileUtils.copyFile() overwrites an existing target and keeps the last-modified time, and Guava’s Files.copy(File, File) also overwrites. The fully qualified name com.google.common.io.Files is needed because the class clashes with java.nio.file.Files.
The approaches differ most in whether they overwrite an existing target, which is the first thing to check when we pick one.
| Approach | Overwrites by default | Best use |
|---|---|---|
| Files.copy(Path, Path) | No | Default choice for files |
| Files.copy(InputStream, Path) | No | Uploads, downloads, classpath resources |
| FileChannel.transferTo() | Depends on open options | Copying a byte range or to a socket |
| InputStream.transferTo() | Yes, with FileOutputStream | Code that already uses java.io streams |
| FileUtils.copyFile() | Yes | Projects that already use Commons IO |
7. Copy a File in Java FAQs
Searches that follow a first working copy ask about overwriting, folders, speed, links and older Java versions.
7.1. How do we overwrite a file if it already exists?
Pass StandardCopyOption.REPLACE_EXISTING as the third argument, as in Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING). Without it, the copy throws FileAlreadyExistsException.
7.2. Does Files.copy() copy a directory and its contents?
No. For a directory, Files.copy() creates an empty directory at the target and copies none of the entries. To copy the contents, we walk the tree with Files.walk() and copy each entry, as the end of section 1.3 points out.
7.3. What is the fastest way to copy a file in Java?
For a whole file, Files.copy(Path, Path) is the method to start with, because the JDK uses the native copy routines of the operating system for it. A hand-written byte loop with a small buffer moves every byte through Java code, one read and one write call per buffer. Measure on the target machine before switching to channels, since the result depends on the disk and the file system.
7.4. Does Files.copy() follow symbolic links?
Yes, by default it copies the file the link points to. Passing LinkOption.NOFOLLOW_LINKS copies the link itself, which the symbolic links guide covers in detail.
7.5. How do we copy a file in Java 8 or older?
The same Files.copy() method exists since Java 7, so the examples in this guide work on Java 8 as well. Only a few helpers in the snippets are newer, namely InputStream.transferTo() (Java 9), ByteArrayOutputStream.toString(Charset) (Java 10), Files.writeString() and Files.readString() (Java 11) and Files.mismatch() (Java 12).
8. Conclusion
The Files.copy() method copies a file in one line, refuses to overwrite unless we pass REPLACE_EXISTING, and keeps timestamps with COPY_ATTRIBUTES. Its stream overloads save uploads and resources to disk, or send a file to any output stream.
We use FileChannel.transferTo() only for ranges and sockets, and for java.io streams only when old code hands us a File. When other programs read the target, a copy to a temporary file followed by an atomic move keeps them from seeing a partial file. The Java IO tutorial lists the related copy, move and delete guides.
9. References
- Files Javadoc (Java 25)
- StandardCopyOption Javadoc
- FileChannel Javadoc
- Dev.java, Manipulating Files and Directories
- Commons IO FileUtils Javadoc
Happy Learning !!
I am using Java8. Which method is the best one?
For small files, I will go with
java.nio.file.Files.copy(). For larger files, I will try first theFileChannel.transferTo()API.