File Copy with Byte Streams
Fast recall
Quick revision
Core answer
InputStream.transferTo(output) or a buffer loop that writes only the returned byte count. Decide replacement, partial-failure cleanup, links, and metadata separately.Interview-ready guide
Interview answer
A byte-stream file copy opens an InputStream for the source and an OutputStream for the target, transfers bytes until the input ends, and closes both resources. Try-with-resources is important because a read or write can fail after only part of the file has been copied.
On modern Java, input.transferTo(output) copies all remaining bytes and returns the number transferred; it does not close either stream. A manual loop is also valid: call read(buffer), stop when it returns -1, and call write(buffer, 0, count). The count must be used because the final read may fill only part of the buffer.
For example, a binary report can be copied from a source Path to a target Path and checked with a checksum when integrity matters. The target rule must be clear. Files.newOutputStream(target) normally creates a missing file and truncates an existing one, while CREATE_NEW prevents an accidental overwrite. Append is usually wrong for a copy because it keeps the old bytes.
A normal stream copy is not an atomic replacement. If writing fails, a partial target may remain. Important files can be written to a temporary file, checked, and then moved into place, using ATOMIC_MOVE only when the file system supports it. For an ordinary path-to-path copy, Files.copy is simpler, but the program still needs a clear policy for replacement, attributes, links, cleanup, and partial failure.
File Copy with Byte Streams
Direct Answer
To copy a file through byte streams, open the source and target in try-with-resources and use InputStream.transferTo(output) or a buffer loop that writes only the returned byte count. Decide replacement, partial-failure cleanup, links, and metadata separately.
Deep Dive & Architecture
Once a target is opened with truncation, readers can observe an empty or partially copied file. Closing successfully proves the stream completed, but it does not make the sequence atomic or automatically preserve every source attribute.
Writing a sibling temporary file first separates construction from publication. After validation, a move can make the new name visible. ATOMIC_MOVE is a request whose support depends on the provider, so the application must decide whether lack of support is fatal.
If the source can change during the copy, comparing final size alone may not prove a stable snapshot. The surrounding system needs locking, versioning, or an immutable source contract when consistency matters.
Code Example
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
class CopyWithByteStreams {
public static void main(String[] args) throws Exception {
Path source = Files.write(Files.createTempFile("source-", ".bin"), new byte[] {1, 2, 3});
Path target = Files.createTempFile("target-", ".bin");
try {
long copied;
try (InputStream in = Files.newInputStream(source);
OutputStream out = Files.newOutputStream(target)) {
copied = in.transferTo(out);
}
System.out.println(copied + " bytes: " + Files.size(target));
} finally {
Files.deleteIfExists(source);
Files.deleteIfExists(target);
}
}
}
Check your understanding
Answer these without looking back at the article.
- Does InputStream.transferTo close either stream?
- Why can a failed copy leave a partial target?
- When is Files.copy simpler than a manual stream copy?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.