Skip to main content

Ch 15.4 Serialization

Objects (instances) created during program execution exist only in JVM heap memory and disappear when the program terminates. Serialization is used when you need to persist an object's state to a file, or transmit it over a network to another JVM.

  • Serialization: Converting an in-memory object into a sequential stream of bytes
  • Deserialization: Reconstructing an object from stored or received byte data

1. The Serializable Interface

A class must implement java.io.Serializable to be serializable. This is a marker interface— it has no methods to implement. It simply signals "this class is safe to serialize."

import java.io.Serializable;

public class UserInfo implements Serializable {
// serialVersionUID: used to verify class compatibility during deserialization
// Highly recommended — if omitted, the JVM auto-generates one that may change
private static final long serialVersionUID = 1L;

String name;

// transient: excluded from serialization (e.g., sensitive data)
transient String password;

int age;

public UserInfo(String name, String password, int age) {
this.name = name;
this.password = password;
this.age = age;
}

@Override
public String toString() {
return String.format("UserInfo{name=%s, password=%s, age=%d}", name, password, age);
}
}
warning

If a class field refers to another object, that object's class must also implement Serializable. Otherwise, a NotSerializableException is thrown at runtime.

2. ObjectOutputStream / ObjectInputStream

Use these dedicated streams to write and read entire objects.

import java.io.*;

public class SerializationBasic {
public static void main(String[] args) {
UserInfo original = new UserInfo("Alice", "secret123", 30);
String fileName = "userinfo.ser";

// --- Serialization: object -> file ---
try (ObjectOutputStream oos = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream(fileName)))) {
oos.writeObject(original);
System.out.println("Serialized: " + original);
} catch (IOException e) {
e.printStackTrace();
}

// --- Deserialization: file -> object ---
try (ObjectInputStream ois = new ObjectInputStream(
new BufferedInputStream(new FileInputStream(fileName)))) {
UserInfo restored = (UserInfo) ois.readObject();
System.out.println("Deserialized: " + restored);
System.out.println("name: " + restored.name); // Alice
System.out.println("age: " + restored.age); // 30
System.out.println("password: " + restored.password); // null (transient!)
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}

Output:

Serialized:   UserInfo{name=Alice, password=secret123, age=30}
Deserialized: UserInfo{name=Alice, password=null, age=30}
name: Alice
age: 30
password: null <- transient field is not persisted

3. serialVersionUID — Version Management

When a class is modified after serialization (fields added/removed), the serialVersionUID prevents accidentally loading incompatible data.

import java.io.Serializable;

public class Product implements Serializable {
private static final long serialVersionUID = 2L; // increment when breaking changes occur

private String name;
private double price;
private String category; // field added in version 2

public Product(String name, double price, String category) {
this.name = name;
this.price = price;
this.category = category;
}

@Override
public String toString() {
return String.format("Product{%s, %.0f, %s}", name, price, category);
}
}
note

Always declare serialVersionUID explicitly. If you don't, the JVM auto-generates it from the class structure. Any change to the class (even adding a field or method) will change the auto-generated UID, causing InvalidClassException when deserializing data from an older version.

4. Serializing Multiple Objects

import java.io.*;
import java.util.*;

public class SerializeList {
public static void main(String[] args) throws IOException, ClassNotFoundException {
List<UserInfo> users = Arrays.asList(
new UserInfo("Alice", "pass1", 30),
new UserInfo("Bob", "pass2", 25),
new UserInfo("Carol", "pass3", 35)
);

String fileName = "users.ser";

// Write the entire list as a single object
try (ObjectOutputStream oos = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream(fileName)))) {
oos.writeObject(users);
System.out.println("Saved " + users.size() + " users.");
}

// Read back the entire list
try (ObjectInputStream ois = new ObjectInputStream(
new BufferedInputStream(new FileInputStream(fileName)))) {
@SuppressWarnings("unchecked")
List<UserInfo> restored = (List<UserInfo>) ois.readObject();
restored.forEach(u -> System.out.println(" " + u.name + ", age=" + u.age));
}
}
}

5. Custom Serialization with readObject/writeObject

You can customize the serialization process by implementing writeObject and readObject methods.

import java.io.*;

public class SecureCredentials implements Serializable {
private static final long serialVersionUID = 1L;

private String username;
private transient String password; // exclude from default serialization

public SecureCredentials(String username, String password) {
this.username = username;
this.password = password;
}

// Custom serialization: encrypt the password before writing
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject(); // write non-transient fields normally
// Store an obfuscated form (real apps should use proper encryption)
oos.writeObject(obfuscate(password));
}

// Custom deserialization: decrypt the password after reading
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // read non-transient fields normally
this.password = deobfuscate((String) ois.readObject());
}

private String obfuscate(String s) {
if (s == null) return null;
return new StringBuilder(s).reverse().toString(); // simple reverse
}

private String deobfuscate(String s) {
return obfuscate(s); // reversing again gives original
}

@Override
public String toString() {
return "Credentials{user=" + username + ", pass=" + password + "}";
}
}

6. transient — Excluding Fields

Fields marked with transient are skipped during serialization. Use it for:

  • Sensitive data (passwords, tokens)
  • Non-serializable objects (connections, threads)
  • Derived/calculated values (can be recomputed)
import java.io.Serializable;
import java.util.Date;

public class Session implements Serializable {
private static final long serialVersionUID = 1L;

private String userId;
private String sessionToken;
private Date loginTime;

// Excluded: derived value (can be recalculated)
private transient long sessionDurationMs;

// Excluded: sensitive token should not be persisted to disk
private transient String internalApiKey;

// Excluded: non-serializable (would throw NotSerializableException)
// private transient java.sql.Connection dbConn;

public Session(String userId, String token) {
this.userId = userId;
this.sessionToken = token;
this.loginTime = new Date();
this.sessionDurationMs = 0;
this.internalApiKey = "sk-live-abc123";
}
}

7. Security Risks and the JSON Alternative

Java's built-in serialization has well-known security vulnerabilities. Deserialization of untrusted data is a critical security risk(CVE-listed vulnerabilities exist in popular libraries).

// Dangerous: deserializing data from an untrusted source
ObjectInputStream ois = new ObjectInputStream(untrustedInputStream);
Object obj = ois.readObject(); // can execute arbitrary code!

In modern distributed applications and web services, JSON serialization is the standard choice:

// Using Jackson (the most popular Java JSON library)
// pom.xml: com.fasterxml.jackson.core:jackson-databind

import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper mapper = new ObjectMapper();

// Serialize to JSON string
UserInfo user = new UserInfo("Alice", "secret", 30);
String json = mapper.writeValueAsString(user);
System.out.println(json); // {"name":"Alice","age":30}

// Deserialize from JSON string
UserInfo restored = mapper.readValue(json, UserInfo.class);
System.out.println(restored.name); // Alice
FeatureJava SerializationJSON (Jackson/Gson)
FormatBinary (.ser)Text (human-readable)
SecurityHigh riskSafe
Language interopJava onlyAll languages
Schema evolutionFragile (serialVersionUID)Flexible
PerformanceModerateFast (Jackson)
RecommendationLegacy / internal use onlyPreferred for all new code

8. Practical Example: Persistent Application State

import java.io.*;
import java.util.*;

public class PersistentGameState {

static class GameState implements Serializable {
private static final long serialVersionUID = 1L;

String playerName;
int level;
int score;
List<String> achievements;
transient long sessionStartTime; // not persisted

public GameState(String playerName) {
this.playerName = playerName;
this.level = 1;
this.score = 0;
this.achievements = new ArrayList<>();
this.sessionStartTime = System.currentTimeMillis();
}

void addAchievement(String achievement) {
achievements.add(achievement);
System.out.println("Achievement unlocked: " + achievement);
}

@Override
public String toString() {
return String.format("GameState{player=%s, level=%d, score=%d, achievements=%s}",
playerName, level, score, achievements);
}
}

static final String SAVE_FILE = "savegame.dat";

static void saveGame(GameState state) throws IOException {
try (ObjectOutputStream oos = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream(SAVE_FILE)))) {
oos.writeObject(state);
System.out.println("Game saved.");
}
}

static GameState loadGame() throws IOException, ClassNotFoundException {
File file = new File(SAVE_FILE);
if (!file.exists()) return null;
try (ObjectInputStream ois = new ObjectInputStream(
new BufferedInputStream(new FileInputStream(file)))) {
return (GameState) ois.readObject();
}
}

public static void main(String[] args) throws IOException, ClassNotFoundException {
// Try to load existing save
GameState state = loadGame();
if (state == null) {
state = new GameState("Hero");
System.out.println("New game started.");
} else {
System.out.println("Game loaded: " + state);
}

// Simulate gameplay
state.level += 1;
state.score += 1500;
state.addAchievement("First Victory");
state.addAchievement("Level 2 Reached");

System.out.println("Current state: " + state);
saveGame(state);
}
}
tip

Pro tip: For new projects, avoid Java's built-in object serialization for external communication or persistent storage. Use JSON with Jackson or Gson, XML with JAXB, or Protocol Buffers instead. Java serialization is fine for JVM-internal caching or session replication within a trusted environment, but never for reading untrusted external data.