Java FileChannel transferTo() and transferFrom() Examples

FileChannel transferTo() and transferFrom() examples. Copy and merge files, send a file to a socket, save streams, and loop past the 2 GB per-call limit.

A read and write loop copies bytes from the page cache into a Java buffer and back, while transferTo moves them inside the kernel from the page cache to the socket or file

The FileChannel.transferTo() method copies bytes from a file channel to any writable channel, and FileChannel.transferFrom() copies bytes from any readable channel into a file channel, without passing them through a buffer in our code. On Linux, the JDK implements both methods with system calls such as sendfile and copy_file_range, so the operating system moves the bytes from the page cache to the target.

We use the two methods to copy and merge files, to send a file over a socket in a download server, and to save an incoming stream into a file. Because the data does not land in the Java heap, a large transfer creates no garbage.

The following example duplicates a file with transferTo() and repeats the call until every byte is transferred.

Path source = Files.writeString(Files.createTempFile("invoice", ".txt"), "total=120");
Path target = Files.createTempFile("invoice-copy", ".txt");
try (FileChannel in = FileChannel.open(source, StandardOpenOption.READ);
     FileChannel out = FileChannel.open(target, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
    long size = in.size();                              // 9
    long position = 0;
    while (position < size) {
        position += in.transferTo(position, size - position, out);
    }
}
String copy = Files.readString(target);                 // "total=120"

Notice the loop. One call may transfer fewer bytes than requested, and the method does not move the position of the source channel, so we track the position ourselves. We look at how the methods work, copy and merge files, send a file to a socket, save a stream into a file, and compare the methods with Files.copy() and InputStream.transferTo().

1. Data Transfer Between Channels

Copying data between channels with a read and write loop moves every byte twice. A read() copies the bytes from the page cache into our buffer, and a write() copies them back into the kernel. The two transfer methods let the operating system do the copy, and many systems move the bytes from the page cache to the target with no copy into the process at all. That technique is called zero-copy.

A read and write loop copies bytes from the page cache into a Java buffer and back, while transferTo moves them inside the kernel from the page cache to the socket or file
With transferTo() the bytes stay in the kernel, so the copy needs no Java buffer and fewer system calls

Both methods are declared in FileChannel, so one side of every transfer must be a file channel. The other side can be any channel, for example a SocketChannel, a Pipe channel or a channel adapted from a stream.

// does not compile: declarations from FileChannel, shown for reference
public abstract long transferTo(long position, long count, WritableByteChannel target) throws IOException;
public abstract long transferFrom(ReadableByteChannel src, long position, long count) throws IOException;

The two methods follow the same rules, and the position handling is where most bugs start.

  • The position argument is a position in the file, and the file channel’s own position does not change.
  • The return value is the number of bytes transferred, which can be less than count. A transfer from a position at or past the end of the file returns 0.
  • If the other channel has a position, such as a target FileChannel, the bytes go to that position and the position advances.
  • The source must be open for reading and the target for writing, otherwise the call throws NonReadableChannelException or NonWritableChannelException.

Two socket channels cannot transfer to each other with these methods. A proxy that forwards one socket to another still needs a buffer loop.

2. FileChannel transferTo() and transferFrom() Examples

The examples use FileChannel.open() and try-with-resources, so every channel closes even when a transfer fails with an IOException.

2.1. Simple File Copy Example

The intro example copies with transferTo(). The same copy with transferFrom() starts from the target channel. Open the target with TRUNCATE_EXISTING or CREATE_NEW, because without it an older, longer target keeps its extra bytes at the end.

static long copyWithTransferFrom(Path source, Path target) throws IOException {
    try (FileChannel in = FileChannel.open(source, StandardOpenOption.READ);
         FileChannel out = FileChannel.open(target, StandardOpenOption.WRITE,
                 StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
        long size = in.size();
        long position = 0;
        while (position < size) {
            position += out.transferFrom(in, position, size - position);
        }
        return position;
    }
}
Path source = Files.writeString(Files.createTempFile("song", ".txt"), "verse");
Path target = Files.writeString(Files.createTempFile("old", ".txt"), "much longer old content");
long copied = copyWithTransferFrom(source, target);     // 5
String result = Files.readString(target);               // "verse"

2.2. Merging Content From Multiple Files Into One

Merging files is a natural fit for transferTo(), because the target FileChannel advances its own position after each transfer. For example, a nightly job joins the hourly log files of a service into one daily file before it uploads the file to archive storage.

static void merge(List<Path> parts, Path target) throws IOException {
    try (FileChannel out = FileChannel.open(target, StandardOpenOption.WRITE,
            StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
        for (Path part : parts) {
            try (FileChannel in = FileChannel.open(part, StandardOpenOption.READ)) {
                long size = in.size();
                long position = 0;
                while (position < size) {
                    position += in.transferTo(position, size - position, out);
                }
            }
        }
    }
}
Path dir = Files.createTempDirectory("logs");
Path h1 = Files.writeString(dir.resolve("01.log"), "start\n");
Path h2 = Files.writeString(dir.resolve("02.log"), "login\n");
Path h3 = Files.writeString(dir.resolve("03.log"), "stop\n");
Path daily = dir.resolve("daily.log");
merge(List.of(h1, h2, h3), daily);
String merged = Files.readString(daily);                // "start\nlogin\nstop\n"

2.3. Sending a File to a Socket

A download server is the classic use of transferTo(). Say a media app serves video files of several hundred megabytes. With a buffer loop, every byte travels from the page cache into the Java process and back into the socket. With transferTo(), the kernel sends the file pages to the socket, so the server uses less CPU per download and no heap memory for the file.

static void sendFile(Path file, SocketChannel socket) throws IOException {
    try (FileChannel in = FileChannel.open(file, StandardOpenOption.READ)) {
        long size = in.size();
        long position = 0;
        while (position < size) {
            position += in.transferTo(position, size - position, socket);
        }
    }
}

A blocking SocketChannel may still accept fewer bytes than requested, and a non-blocking one returns 0 when its send buffer is full, so the loop is required here too. Web servers such as Tomcat use the same system call for static files when sendfile support is enabled.

The target does not need to be a socket or a file. Any WritableByteChannel works, and the JDK falls back to an internal buffer loop when no faster path exists. The next snippet transfers into a channel over a ByteArrayOutputStream.

Path file = Files.writeString(Files.createTempFile("poem", ".txt"), "roses");
ByteArrayOutputStream memory = new ByteArrayOutputStream();
try (FileChannel in = FileChannel.open(file);
     WritableByteChannel target = Channels.newChannel(memory)) {
    long sent = in.transferTo(0, in.size(), target);    // 5
}
String received = memory.toString(StandardCharsets.UTF_8);   // "roses"

2.4. Saving a Stream Into a File With transferFrom()

The transferFrom() method reads from any ReadableByteChannel, so it can save an InputStream, such as an HTTP response body, into a file. A stream-backed channel has no size, so we loop until a call returns 0, which means the source has no more bytes.

static long save(InputStream in, Path target) throws IOException {
    try (ReadableByteChannel src = Channels.newChannel(in);
         FileChannel out = FileChannel.open(target, StandardOpenOption.WRITE,
                 StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
        long position = 0;
        long n;
        while ((n = out.transferFrom(src, position, 1024 * 1024)) > 0) {
            position += n;
        }
        return position;
    }
}
Path target = Files.createTempFile("download", ".bin");
long saved = save(new ByteArrayInputStream(new byte[3000]), target);   // 3000
long size = Files.size(target);                         // 3000

For a plain stream-to-file copy, Files.copy(InputStream, Path, CopyOption…) does the same in one line. The transferFrom() version is useful when the target is a channel we already hold, for example a file we write at a specific position.

3. Partial Transfers and the 2 GB Limit

A single transferTo() call never moves more than about 2 GB on Linux, so code that skips the loop truncates large files without an error. The JDK caps one direct transfer at 0x7ffff000 bytes (2,147,479,552), which is the limit of the sendfile system call. A 5 GB file needs at least three calls.

The same loop handles the other reasons for a short transfer. A non-blocking target accepts only what fits into its buffer, and a file can shrink while we copy it. In every case, the loop adds the returned count to the position and calls the method again.

Path file = Files.writeString(Files.createTempFile("short", ".txt"), "abc");
try (FileChannel in = FileChannel.open(file);
     WritableByteChannel sink = Channels.newChannel(OutputStream.nullOutputStream())) {
    long pastEnd = in.transferTo(10, 5, sink);          // 0, position is past the end
    long fewer = in.transferTo(1, 100, sink);           // 2, only 2 bytes are left
    long unchanged = in.position();                     // 0
}

4. transferTo() vs Files.copy() vs InputStream.transferTo()

Java has three methods with similar names, and they solve different problems. For copying one file to another path, Files.copy() is the simplest choice, because it also handles options such as REPLACE_EXISTING and COPY_ATTRIBUTES.

MethodSinceWorks withTypical use
FileChannel.transferTo(), transferFrom()Java 1.4A file channel and any channelSockets, merging, writing at a file position
Files.copy(Path, Path, CopyOption…)Java 7Two pathsCopying a file or replacing a target
Files.copy(InputStream, Path, …), Files.copy(Path, OutputStream)Java 7A path and a streamSaving a download, sending a file to a response stream
InputStream.transferTo(OutputStream)Java 9Any two streamsCopying between streams of any kind

The copy files tutorial compares the path-based options in detail, and InputStream to OutputStream covers stream copies.

5. FileChannel transferTo() FAQs

A copy that works in tests can still go wrong in production, which is where the questions about these two methods come from.

5.1. Is FileChannel.transferTo() zero-copy?

Yes, where the operating system supports it. On Linux, the JDK uses sendfile for file-to-socket transfers and copy_file_range for file-to-file transfers, so the bytes do not enter the Java process. Where no such call exists, the JDK uses a mapped or buffered copy instead, and the method still works.

5.2. Does transferTo() change the position of the FileChannel?

No. The source position stays where it was, and the position argument decides where reading starts. The target channel, if it has a position, advances by the number of bytes written.

5.3. Why does transferTo() copy only part of a large file?

One call moves at most about 2 GB on Linux, and a non-blocking target can accept fewer bytes. The method returns the count it transferred, so we call it in a loop until the position reaches the file size.

5.4. What is the difference between transferTo() and transferFrom()?

The transferTo() method is called on the source file channel and writes to any channel. The transferFrom() method is called on the target file channel and reads from any channel. For file-to-file copies, both give the same result.

5.5. Can we use transferTo() between two socket channels?

No. Both methods belong to FileChannel, so one side must be a file. To forward bytes between two sockets, we read into a ByteBuffer and write it to the other socket.

6. Conclusion

The transferTo() and transferFrom() methods copy data between a file channel and another channel without a buffer in our code, and on Linux the kernel does the copy. They fit download servers, file merges and saving streams into files.

Always call them in a loop, because one call can transfer fewer bytes than requested and at most about 2 GB. For a simple path-to-path copy, Files.copy() is shorter, and every other file operation has its own page in the Java IO tutorial.

7. References

Happy Learning !!

Source Code on Github

Leave a Comment

  1. Hi Lokesh,

    I am very big fan of your blog and read as much as time permits me. I have a request for you that i have only limited access for internet so i am not read your article as i am offline. So can you please give an option to download as a PDF formate. I have tried to save the web pages but some time it miss the diagram and code snippets than it’s not useful in that case.
    Thanks
    Abhinav

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.