Skip to main content

Data and Object Streams

Beyond simple bytes and text, Java programs frequently need to read and write more complex structures. Java provides Data Streams for working with primitive data types, and Object Streams for converting entire Java objects into streams of bytes—a process called Serialization.


1. Data Streams

DataInputStream and DataOutputStream allow you to write and read Java primitive data types (boolean, char, byte, short, int, long, float, double) and strings (UTF-8) directly to/from binary files.

Key Methods

  • writeInt(int v) / readInt()
  • writeDouble(double v) / readDouble()
  • writeUTF(String str) / readUTF() (UTF-8 format)

Because the data is written in a standard, platform-independent binary format, a file written by a DataOutputStream on one machine can be read by a DataInputStream on a completely different OS or architecture.


2. Object Streams (Serialization)

Serialization is the mechanism of converting the state of a Java object into a byte stream. This byte stream can then be written to a file, stored in a database, or sent over a network.

Deserialization is the reverse process, where the byte stream is used to recreate the actual Java object in memory.

The core classes involved are ObjectOutputStream (contains writeObject()) and ObjectInputStream (contains readObject()).

The Serializable Interface

To make a class eligible for serialization, it must implement the java.io.Serializable interface. This is a marker interface (it has no methods or fields). It simply signals to the JVM that objects of this class are safe to serialize.

The transient Keyword

When serializing an object, you may not want to serialize certain sensitive fields (like passwords, API keys) or fields that do not make sense to persist (like open database connections or thread status).

Marking a field with the transient keyword instructs the JVM to skip that field during serialization. During deserialization, transient fields are initialized with their default values (e.g. null for objects, 0 for numbers).

serialVersionUID

The serialVersionUID is a unique version identifier for each Serializable class. It is used during deserialization to ensure that the class that serialized the object is compatible with the class definition you are loading.

private static final long serialVersionUID = 1L;

[!WARNING] If you do not explicitly define a serialVersionUID, the Java compiler will automatically generate one based on class details (fields, methods). If you later modify the class (e.g., adding a field), the compiler will generate a different UID, causing deserialization of older files to fail with an InvalidClassException. Therefore, always define it explicitly.


Practical Code Example: Serializing a User Object

Here is a complete program that serializes a User object (with a transient password field) to a file, and then deserializes it to verify the results.

Step 1: The Serializable Class

import java.io.Serializable;

public class User implements Serializable {
// Explicitly define serialVersionUID
private static final long serialVersionUID = 1234567L;

private String username;
private int age;

// This field will not be serialized
private transient String password;

public User(String username, int age, String password) {
this.username = username;
this.age = age;
this.password = password;
}

@Override
public String toString() {
return "User{username='" + username + "', age=" + age + ", password='" + password + "'}";
}
}

Step 2: Running Serialization and Deserialization

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;

public class SerializationDemo {
public static void main(String[] args) {
String filename = "user.ser";
User originalUser = new User("alice_dev", 28, "SuperSecret123");

// 1. Serialize the Object
System.out.println("Serializing: " + originalUser);
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename))) {
oos.writeObject(originalUser);
System.out.println("Object successfully serialized to " + filename);
} catch (IOException e) {
e.printStackTrace();
}

System.out.println("\n------------------------------------\n");

// 2. Deserialize the Object
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename))) {
User deserializedUser = (User) ois.readObject();
System.out.println("Deserialized: " + deserializedUser);

// Notice that password is null because it was marked transient
if (deserializedUser.toString().contains("password='null'")) {
System.out.println("Success: The transient password field was not serialized!");
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}