CodeGym /Courses /JAVA 25 SELF /DataInputStream, DataOutputStream: working with primitive...

DataInputStream, DataOutputStream: working with primitives

JAVA 25 SELF
Level 36 , Lesson 3
Available

1. Why do we need DataInputStream and DataOutputStream?

When you work with files, sometimes you need to store not just text but structured data: numbers, boolean values, arrays of primitives. For example, imagine you’re writing a simple game and want to save the user’s progress: number of points (int), current level (int), play time (double), winner status (boolean). Of course, you could write this in text form:

12345
5
67.5
true

But that’s inconvenient and brittle: you have to parse strings, the format may “drift,” and numbers take more space.

The idea is to write data to a file in “raw” (binary) form, without converting to text. For this, Java has two excellent classes:

  • DataOutputStream — can write primitive types to a stream.
  • DataInputStream — can read primitive types from a stream.

They work on top of regular byte streams (OutputStream/InputStream). That is, they are “wrappers” that don’t just write bytes but know how to assemble those bytes into an int, double, boolean, and even a String.

How does it work?

A regular FileOutputStream can be thought of as a conveyor belt onto which you manually place bytes one after another. If you want to write an integer or a string, you have to track yourself how many bytes each element takes.

DataOutputStream makes life easier: it acts like a robot on this conveyor. You tell it “write a number” or “write a string,” and it packs the data into the required number of bytes and sends it to disk. On the other end of the conveyor, the same kind of robot — DataInputStream — can assemble those bytes back into the original objects.

Why is this convenient? Because you don’t need to think about the number of bytes for int, double, or boolean. The data is stored compactly, read and written quickly, and there’s no risk of parsing errors or format issues.

2. Example: Writing and reading primitives

Suppose we want to save the results of our (hypothetical) game: player name (String), number of points (int), record time (double), whether the player won (boolean).

Writing data to a file

import java.io.*;

public class SaveGameData {
    public static void main(String[] args) {
        String fileName = "savegame.bin";
        String playerName = "Alice";
        int score = 12345;
        double recordTime = 67.5;
        boolean isWinner = true;

        try (DataOutputStream dos = new DataOutputStream(
                new FileOutputStream(fileName))) {
            dos.writeUTF(playerName);    // Write a string (UTF-8)
            dos.writeInt(score);         // Write int (4 bytes)
            dos.writeDouble(recordTime); // Write double (8 bytes)
            dos.writeBoolean(isWinner);  // Write boolean (1 byte)
            System.out.println("Data successfully written to the file!");
        } catch (IOException e) {
            System.out.println("Write error: " + e.getMessage());
        }
    }
}
  • writeUTF(String) — writes a string in UTF-8 format (with a length prefix).
  • writeInt(int) — writes 4 bytes.
  • writeDouble(double) — writes 8 bytes.
  • writeBoolean(boolean) — writes 1 byte (1 or 0).
  • All methods automatically “pack” the data in the required format.

Reading data from a file

import java.io.*;

public class LoadGameData {
    public static void main(String[] args) {
        String fileName = "savegame.bin";

        try (DataInputStream dis = new DataInputStream(
                new FileInputStream(fileName))) {
            String playerName = dis.readUTF();      // Read a string
            int score = dis.readInt();              // Read int
            double recordTime = dis.readDouble();   // Read double
            boolean isWinner = dis.readBoolean();   // Read boolean

            System.out.println("Player name: " + playerName);
            System.out.println("Score: " + score);
            System.out.println("Time: " + recordTime);
            System.out.println("Winner: " + isWinner);
        } catch (IOException e) {
            System.out.println("Read error: " + e.getMessage());
        }
    }
}

Important point:
The read order must match the write order! If you first wrote a string, then an int, then a double — you need to read in the same order. Otherwise, you’ll get an error or garbage data.

3. Which types are supported?

DataOutputStream and DataInputStream support all the main Java primitive types:

Write method Read method Data type Size (bytes)
writeBoolean(boolean)
readBoolean()
boolean 1
writeByte(int)
readByte()
byte 1
writeShort(int)
readShort()
short 2
writeChar(int)
readChar()
char 2
writeInt(int)
readInt()
int 4
writeLong(long)
readLong()
long 8
writeFloat(float)
readFloat()
float 4
writeDouble(double)
readDouble()
double 8
writeUTF(String)
readUTF()
String (UTF) variable

Notes:
- For strings, writeUTF/readUTF are most often used (the string length is written first and the bytes in UTF-8).
- If you want to write an array, first write its length, and then its elements one by one.

4. Advanced example: Saving arrays of primitives

Writing an array

int[] scores = {100, 200, 300, 400, 500};
try (DataOutputStream dos = new DataOutputStream(
        new FileOutputStream("scores.bin"))) {
    dos.writeInt(scores.length); // First write the array length
    for (int score : scores) {
        dos.writeInt(score);     // Then each element
    }
}

Reading an array

try (DataInputStream dis = new DataInputStream(
        new FileInputStream("scores.bin"))) {
    int length = dis.readInt();      // Read the length
    int[] scores = new int[length];
    for (int i = 0; i < length; i++) {
        scores[i] = dis.readInt();   // Read elements
    }
    // Print the array
    for (int score : scores) {
        System.out.println(score);
    }
}

Why write the length first?
Because when reading we don’t know how many numbers were written. By writing the length up front, we make the file format self-describing.

5. Important nuances and details

When should you use DataInputStream/DataOutputStream?

  • When you need to save/load structured data consisting of primitives.
  • For exchanging binary data between Java programs (or even different languages, if you know the format).
  • When compactness and speed matter (for example, logs, computation results, large arrays of numbers).

When you shouldn’t:

  • If you need a human-readable format (CSV, JSON, XML) — use text formats.
  • For complex nested objects — it’s better to use serialization via ObjectOutputStream/ObjectInputStream (a separate topic).

Buffering

DataOutputStream and DataInputStream do not buffer data by themselves. If you want to improve performance when working with large files, wrap them in BufferedOutputStream/BufferedInputStream:

try (DataOutputStream dos = new DataOutputStream(
        new BufferedOutputStream(new FileOutputStream("data.bin")))) {
    // ...
}

String encoding

The writeUTF/readUTF methods use a special format: first the string length is written (in bytes), then the content in UTF-8. Don’t confuse this with just writing a byte array!

Exceptions

Read/write operations can throw an IOException if the file is unavailable, corrupted, or ended prematurely. If you try to read more than was written, you’ll often get an EOFException. Always use try-with-resources or handle the exception with try-catch.

Order of reading and writing

The most common mistake is a mismatch between the write and read order. If you wrote: int, double, boolean, but read as double, int, boolean, you’ll get incorrect data or an exception.

6. Common mistakes

Error #1: Mismatched write and read order. If you change the order of methods, the data will be read incorrectly or an exception will be thrown. For example, if you first wrote a string and then a number, but when reading you try to read a number first — you’ll get a format error.

Error #2: Forgot to write the array length. If you write an array of primitives but don’t write its length, when reading you won’t know how many elements to read. This leads either to an error or to “extra” data at the end.

Error #3: Attempting to read past end of file. If you read more data than was written, you’ll get an EOFException (end of file).

Error #4: Using DataInputStream/DataOutputStream for text files. These classes are not intended for reading regular text files created, for example, in Notepad. If you try to read such a file via readInt(), you’ll get meaningless data or an error.

Error #5: Stream not closed. If you don’t use try-with-resources or don’t close streams manually, the file may remain unavailable to other programs or not be fully saved.

1
Task
JAVA 25 SELF, level 36, lesson 3
Locked
Encoding and decoding a secret message ✉️
Encoding and decoding a secret message ✉️
1
Task
JAVA 25 SELF, level 36, lesson 3
Locked
Magical Artifacts Inventory 🧙
Magical Artifacts Inventory 🧙
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION