Java RandomAccessFile: Seek, Read and Write at Arbitrary Positions
Sequential streams like FileInputStream and BufferedReader read a file from start to finish: you cannot jump to position 500 and read from there. RandomAccessFile breaks that restriction: it maintains a file pointer that you can move to any byte offset with seek(), then read or write data at that exact position. This makes it the right tool for binary file formats with fixed-length records, in-place updates, and file headers. This article covers the API, the open modes, typed reads/writes, fixed-length record files, and when to choose FileChannel instead.
What RandomAccessFile Is and When to Use It
Sequential I/O classes (FileInputStreamBufferedReaderFiles.lines()
RandomAccessFileseek(long pos)
Use RandomAccessFileFileChannelposition()RandomAccessFile
Opening a RandomAccessFile: Modes
RandomAccessFileFile
: read-only. Writing throws"r" .IOException : read and write. Creates the file if it does not exist."rw" : read/write with synchronous content+metadata flush to storage after every write. Equivalent to"rws" .StandardOpenOption.SYNC : read/write with synchronous content flush only (metadata updates may be buffered). Slightly faster than"rwd" when metadata durability is not required."rws"
import java.io.RandomAccessFile;
import java.io.IOException;
// Open for reading only
try (RandomAccessFile raf = new RandomAccessFile("data.bin", "r")) {
System.out.println("File length: " + raf.length() + " bytes");
System.out.println("Current pointer: " + raf.getFilePointer()); // 0
}
// Open for reading and writing (creates file if absent)
try (RandomAccessFile raf = new RandomAccessFile("output.bin", "rw")) {
raf.writeInt(42);
raf.writeDouble(3.14);
raf.writeUTF("Hello");
System.out.println("After writes, pointer at: " + raf.getFilePointer());
System.out.println("File length: " + raf.length());
// Seek back to the start and read back
raf.seek(0);
System.out.println(raf.readInt()); // 42
System.out.println(raf.readDouble()); // 3.14
System.out.println(raf.readUTF()); // Hello
}
RandomAccessFileCloseableclose()Fixed-Length Record Files
The most powerful use of RandomAccessFileoffset = index × recordSize
This is the foundation of simple binary databases and index files. The trade-off is that variable-length fields (strings of arbitrary length) must be padded or stored indirectly: a fixed-length record file typically stores strings in fixed-size byte arrays, truncating or padding with null bytes as needed.
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
public class PersonRecord {
// Fixed-length record layout:
// id : 4 bytes (int)
// age : 4 bytes (int)
// name : 32 bytes (fixed-length padded UTF-8)
// Total : 40 bytes per record
static final int RECORD_SIZE = 40;
static final int NAME_BYTES = 32;
static void writeRecord(RandomAccessFile raf, int index,
int id, int age, String name) throws IOException {
raf.seek((long) index * RECORD_SIZE);
raf.writeInt(id);
raf.writeInt(age);
// Write name as fixed 32-byte field (pad with 0s)
byte[] nameBytes = Arrays.copyOf(
name.getBytes(StandardCharsets.UTF_8), NAME_BYTES);
raf.write(nameBytes);
}
static String readName(RandomAccessFile raf, int index) throws IOException {
raf.seek((long) index * RECORD_SIZE + 8); // skip id (4) + age (4)
byte[] nameBytes = new byte[NAME_BYTES];
raf.readFully(nameBytes);
// Trim trailing null bytes
int len = 0;
while (len < NAME_BYTES && nameBytes[len] != 0) len++;
return new String(nameBytes, 0, len, StandardCharsets.UTF_8);
}
public static void main(String[] args) throws IOException {
try (RandomAccessFile raf = new RandomAccessFile("persons.bin", "rw")) {
// Write 3 records
writeRecord(raf, 0, 1, 30, "Alice");
writeRecord(raf, 1, 2, 25, "Bob");
writeRecord(raf, 2, 3, 35, "Carol");
// Read record 1 (Bob) directly — O(1) access
System.out.println("Record 1 name: " + readName(raf, 1)); // Bob
// Update record 0 (Alice) age in-place
raf.seek(0L * RECORD_SIZE + 4); // skip id, position at age
raf.writeInt(31);
// Read record 2 (Carol) name
System.out.println("Record 2 name: " + readName(raf, 2)); // Carol
System.out.println("Total records: " + raf.length() / RECORD_SIZE); // 3
}
}
}
RandomAccessFile vs FileChannel: When to Choose Each
RandomAccessFile
FileChannelRandomAccessFile.getChannel()FileChannel.open()map()transferTo()transferFrom()FileChannel
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.*;
// FileChannel equivalent of RandomAccessFile random access
try (FileChannel fc = FileChannel.open(
Path.of("data.bin"),
StandardOpenOption.READ, StandardOpenOption.WRITE,
StandardOpenOption.CREATE)) {
// Write at position 0
ByteBuffer buf = ByteBuffer.allocate(8);
buf.putInt(42).putDouble(3.14).flip(); // ← flip to switch from write to read mode...
// Actually for separate writes:
ByteBuffer ibuf = ByteBuffer.allocate(4);
ibuf.putInt(42);
ibuf.flip();
fc.write(ibuf, 0); // write int at position 0
// Read back at position 0
ibuf.clear();
fc.read(ibuf, 0);
ibuf.flip();
System.out.println("Value: " + ibuf.getInt()); // 42
// Get a RandomAccessFile's channel
try (RandomAccessFile raf = new RandomAccessFile("existing.bin", "rw");
FileChannel channel = raf.getChannel()) {
// now use channel for high-throughput I/O
System.out.println("Channel size: " + channel.size());
}
}
| Feature | RandomAccessFile | FileChannel |
|---|---|---|
| Seek to arbitrary position | ✅ seek() | ✅ position() |
| Typed read/write (int, double, UTF) | ✅ Built-in | ❌ Use ByteBuffer manually |
| Memory-mapped files | ❌ | ✅ map() |
| Zero-copy transfer | ❌ | ✅ transferTo/From |
| NIO non-blocking | ❌ | ✅ |
| Simplicity | High | Medium |