Skip to content
Java file handling 7 min read

RandomAccessFile

Most file APIs force you to start at the beginning and work forward — but sometimes you need to jump straight to byte 10 000, update four bytes, and leave the rest untouched. RandomAccessFile gives you exactly that power: a single object that can both read and write any position in a file using an internal file pointer you control directly.

What Is RandomAccessFile?

RandomAccessFile lives in java.io. Unlike the stream classes, it does not extend InputStream or OutputStream — it implements both DataInput and DataOutput directly, bundling read and write support into one class. Think of it as treating a file like a big byte array you can index into at will.

Key characteristics at a glance:

FeatureRandomAccessFile
ExtendsObject
ImplementsDataInput, DataOutput, Closeable
DirectionRead, write, or both
Position controlseek(long pos)
Default pointer0 (start of file)
Thread safetyNot thread-safe

Note: For pure sequential reads or writes, prefer FileInputStream / FileOutputStream wrapped in a BufferedInputStream. RandomAccessFile shines when you genuinely need non-sequential access.

Opening a RandomAccessFile

The constructor takes a file path (or a File object) and a mode string:

import java.io.RandomAccessFile;

public class OpenExample {
    public static void main(String[] args) throws Exception {
        // "r"  — read-only
        RandomAccessFile reader = new RandomAccessFile("data.bin", "r");

        // "rw" — read and write; creates the file if it doesn't exist
        RandomAccessFile rw = new RandomAccessFile("data.bin", "rw");

        reader.close();
        rw.close();
    }
}

Mode options:

ModeMeaning
"r"Read-only. Throws FileNotFoundException if the file doesn’t exist.
"rw"Read/write. Creates the file if it doesn’t exist.
"rws"Read/write + sync file content and metadata to storage on every write.
"rwd"Read/write + sync file content (but not metadata) on every write.

"rws" and "rwd" are useful for durability-critical code (e.g., write-ahead logs) but are much slower because every write flushes to disk.

The File Pointer

Every RandomAccessFile maintains a file pointer — an offset (in bytes from the start of the file) indicating where the next read or write will happen. After each read or write operation the pointer advances automatically by the number of bytes transferred.

import java.io.RandomAccessFile;

public class PointerDemo {
    public static void main(String[] args) throws Exception {
        try (RandomAccessFile raf = new RandomAccessFile("demo.dat", "rw")) {
            System.out.println("Initial pointer: " + raf.getFilePointer()); // 0

            raf.writeInt(42);   // writes 4 bytes
            System.out.println("After writeInt: " + raf.getFilePointer()); // 4

            raf.seek(0);        // jump back to start
            System.out.println("After seek(0):  " + raf.getFilePointer()); // 0

            int value = raf.readInt();
            System.out.println("Read value:     " + value);               // 42
        }
    }
}

Output:

Initial pointer: 0
After writeInt: 4
After seek(0):  0
Read value:     42

Two essential pointer methods:

  • long getFilePointer() — returns the current byte offset.
  • void seek(long pos) — moves the pointer to pos. You can seek past the end of file; writing there extends the file.

Writing Data

Because RandomAccessFile implements DataOutput, it has typed write methods that match the primitive data types exactly:

import java.io.RandomAccessFile;

public class WriteExample {
    public static void main(String[] args) throws Exception {
        try (RandomAccessFile raf = new RandomAccessFile("record.dat", "rw")) {
            raf.writeInt(1001);          // employee ID  — 4 bytes
            raf.writeDouble(75_000.50);  // salary       — 8 bytes
            raf.writeBoolean(true);      // active flag  — 1 byte
            raf.writeUTF("Alice");       // name         — 2 + chars bytes
        }
        System.out.println("Written successfully.");
    }
}

Output:

Written successfully.

Tip: writeUTF prepends a 2-byte length, making it easy to read back with readUTF. If you need fixed-width records (important for random access!), write names as fixed-length byte arrays instead.

Reading Data

The symmetric DataInput methods read back exactly what was written:

import java.io.RandomAccessFile;

public class ReadExample {
    public static void main(String[] args) throws Exception {
        try (RandomAccessFile raf = new RandomAccessFile("record.dat", "r")) {
            int    id     = raf.readInt();
            double salary = raf.readDouble();
            boolean active = raf.readBoolean();
            String name   = raf.readUTF();

            System.out.println("ID:     " + id);
            System.out.println("Salary: " + salary);
            System.out.println("Active: " + active);
            System.out.println("Name:   " + name);
        }
    }
}

Output:

ID:     1001
Salary: 75000.5
Active: true
Name:   Alice

Updating a Record In-Place

This is where RandomAccessFile truly earns its name. Given a fixed-record file, you can jump to any record and overwrite it without touching the surrounding data:

import java.io.RandomAccessFile;

public class UpdateRecord {
    // Each record: int (4) + double (8) = 12 bytes (ignoring name for simplicity)
    private static final int RECORD_SIZE = 12;

    public static void main(String[] args) throws Exception {
        // Write three simple records
        try (RandomAccessFile raf = new RandomAccessFile("employees.dat", "rw")) {
            writeRecord(raf, 1, 50_000.0);
            writeRecord(raf, 2, 60_000.0);
            writeRecord(raf, 3, 70_000.0);
        }

        // Update record 2's salary to 65_000.0
        try (RandomAccessFile raf = new RandomAccessFile("employees.dat", "rw")) {
            long offset = (long) (2 - 1) * RECORD_SIZE; // zero-based index
            raf.seek(offset + 4);                        // skip the int ID (4 bytes)
            raf.writeDouble(65_000.0);
        }

        // Read back all three records
        try (RandomAccessFile raf = new RandomAccessFile("employees.dat", "r")) {
            for (int i = 0; i < 3; i++) {
                int    id     = raf.readInt();
                double salary = raf.readDouble();
                System.out.println("ID " + id + " -> $" + salary);
            }
        }
    }

    private static void writeRecord(RandomAccessFile raf, int id, double salary)
            throws Exception {
        raf.writeInt(id);
        raf.writeDouble(salary);
    }
}

Output:

ID 1 -> $50000.0
ID 2 -> $65000.0
ID 3 -> $70000.0

Notice record 2 was updated surgically — records 1 and 3 were never rewritten.

Useful Methods at a Glance

MethodDescription
seek(long pos)Move file pointer to pos
getFilePointer()Return current pointer position
length()Return file length in bytes
setLength(long len)Truncate or extend the file
read()Read a single byte (-1 at EOF)
read(byte[] b)Fill byte array from current position
readFully(byte[] b)Read exactly b.length bytes or throw
skipBytes(int n)Advance pointer by n bytes
readLine()Read a line as a String (ASCII only)

Warning: readLine() in RandomAccessFile reads bytes and converts them as ISO-8859-1. For proper Unicode line reading, use BufferedReader instead.

Under the Hood

At the OS level, RandomAccessFile maps to a single file descriptor opened with both read and write flags. Calls to seek() translate directly to the lseek system call (or SetFilePointer on Windows), making position changes O(1) — there is no data copying or file rewinding happening.

The typed readInt(), readDouble() etc. read the appropriate number of bytes and reassemble them in big-endian (network byte order) format, regardless of the host CPU. This guarantees that a file written on an x86 machine reads correctly on ARM or any other architecture — important for cross-platform binary formats.

Buffering: RandomAccessFile does not have its own application-level buffer (unlike BufferedInputStream). Every read/write goes through a thin JNI layer to the OS. The OS page cache does buffer at the kernel level, so sequential access is still reasonably fast. If you are doing many small reads at adjacent positions, batching them into readFully(byte[]) reduces JNI overhead significantly.

Extending the file: Seeking past the end of file and writing there causes the file to grow. On most systems the gap between the old end and the new write is filled with zero bytes (a sparse file on Linux/NTFS, an actual allocation on older filesystems).

NIO alternative: java.nio.channels.FileChannel (accessible via FileChannel.open() or fileInputStream.getChannel()) offers the same random-access capability with better performance for large transfers, memory-mapped I/O (map()), and explicit locking. For new code that needs heavy random I/O, consider FileChannel — see NIO.2: Path & Files for a starting point.

Common Pitfalls

  • Variable-length records make seeking by index impossible. Either use fixed-length fields or maintain a separate index structure that stores each record’s byte offset.
  • Forgetting to seek before reading back — after a write the pointer sits at the end of what you just wrote. Always seek(0) or seek to the target offset before reading.
  • Not closing the fileRandomAccessFile holds a native file descriptor. Always use try-with-resources.
  • Concurrent accessRandomAccessFile has no built-in synchronization. In multi-threaded code, coordinate access externally or use FileChannel with FileLock.
Last updated June 13, 2026
Was this helpful?