Java Notes
Complete guide to Deserialization in Java — ObjectInputStream, reading objects from files, type safety, version compatibility, security risks, and best practices for safe deserialization.
What is Deserialization?
Deserialization is the reverse of serialization — converting a byte stream back into a Java object. It reconstructs the object's state from the stored bytes.
Basic Deserialization with ObjectInputStream
import java.io.*;
class GameSave implements Serializable {
private static final long serialVersionUID = 1L;
private String playerName;
private int level;
private int score;
private String[] inventory;
private double playTime;
public GameSave(String name, int level, int score, String[] inventory, double hours) {
this.playerName = name;
this.level = level;
this.score = score;
this.inventory = inventory;
this.playTime = hours;
}
@Override
public String toString() {
return String.format(
"GameSave{player='%s', level=%d, score=%d, items=%d, hours=%.1f}",
playerName, level, score, inventory.length, playTime);
}
public void displayFull() {
System.out.println(" Player: " + playerName);
System.out.println(" Level: " + level);
System.out.println(" Score: " + score);
System.out.printf(" Play Time: %.1f hours%n", playTime);
System.out.println(" Inventory: " + String.join(", ", inventory));
}
}
public class DeserializationBasic {
public static void main(String[] args) throws IOException, ClassNotFoundException {
System.out.println("=== Deserialization Demo ===\n");
// First, serialize a game save
GameSave save = new GameSave("DragonSlayer", 42, 158000,
new String[]{"Sword of Fire", "Shield of Light", "Health Potion x5"}, 23.5);
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("game.sav"))) {
oos.writeObject(save);
System.out.println("Game saved! File: game.sav");
System.out.println(" " + save);
}
// Now deserialize (load the game)
System.out.println("\n--- Loading Game Save ---\n");
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("game.sav"))) {
// readObject() returns Object — must cast
Object obj = ois.readObject();
// Type check before casting
if (obj instanceof GameSave) {
GameSave loaded = (GameSave) obj;
System.out.println("Game loaded successfully!");
loaded.displayFull();
}
}
new File("game.sav").delete();
}
}Output:
| Game saved! File | game.sav |
| Player | DragonSlayer |
| Level | 42 |
| Score | 158000 |
| Play Time | 23.5 hours |
| Inventory | Sword of Fire, Shield of Light, Health Potion x5 |
Handling Deserialization Exceptions
import java.io.*;
class SimpleData implements Serializable {
private static final long serialVersionUID = 1L;
String value;
SimpleData(String v) { this.value = v; }
}
public class DeserializationErrors {
public static void main(String[] args) {
System.out.println("=== Deserialization Error Handling ===\n");
// Error 1: File not found
System.out.println("--- Test 1: File not found ---");
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("nonexistent.ser"))) {
ois.readObject();
} catch (FileNotFoundException e) {
System.out.println("✗ FileNotFoundException: " + e.getMessage());
} catch (Exception e) {
System.out.println("Other error: " + e.getMessage());
}
// Error 2: Invalid stream (not a serialized file)
System.out.println("\n--- Test 2: Invalid file format ---");
try {
// Create a plain text file
try (FileWriter fw = new FileWriter("plain.txt")) {
fw.write("This is not serialized data");
}
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("plain.txt"))) {
ois.readObject();
}
} catch (StreamCorruptedException e) {
System.out.println("✗ StreamCorruptedException: " + e.getMessage());
} catch (Exception e) {
System.out.println("✗ " + e.getClass().getSimpleName() + ": " + e.getMessage());
}
// Error 3: ClassNotFoundException
System.out.println("\n--- Test 3: Class not found ---");
System.out.println(" (Happens when deserializing a class that");
System.out.println(" doesn't exist in the classpath)");
System.out.println(" Example: Serialized on Server A, loaded on Server B");
System.out.println(" which doesn't have that class → ClassNotFoundException");
// Error 4: InvalidClassException (version mismatch)
System.out.println("\n--- Test 4: Version mismatch ---");
System.out.println(" (Happens when serialVersionUID doesn't match)");
System.out.println(" Example: Serialize with UID=1, modify class to UID=2,");
System.out.println(" then try to deserialize → InvalidClassException");
// Proper error handling pattern
System.out.println("\n--- Recommended Error Handling Pattern ---");
deserializeSafely("plain.txt");
new File("plain.txt").delete();
}
static Object deserializeSafely(String filename) {
File file = new File(filename);
if (!file.exists()) {
System.out.println(" File not found: " + filename);
return null;
}
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(file))) {
Object obj = ois.readObject();
System.out.println(" ✓ Successfully deserialized: " + obj.getClass().getName());
return obj;
} catch (InvalidClassException e) {
System.out.println(" ✗ Class version mismatch. Data may be outdated.");
System.out.println(" Details: " + e.getMessage());
} catch (StreamCorruptedException e) {
System.out.println(" ✗ File is corrupted or not a serialized object.");
} catch (ClassNotFoundException e) {
System.out.println(" ✗ Required class not found: " + e.getMessage());
} catch (IOException e) {
System.out.println(" ✗ I/O error: " + e.getMessage());
}
return null;
}
}Deserializing Multiple Objects
Output:
| [10 | 00:01] INFO: Server started |
| [10 | 00:05] INFO: DB connected |
| [10 | 01:23] WARN: High CPU usage |
| [10 | 02:45] ERROR: Request timeout |
| [10 | 03:00] INFO: Service recovered |
| --- Better | Serialize as List --- |
Version Compatibility and Class Evolution
import java.io.*;
// Version 1 of the class
class AppSettings implements Serializable {
private static final long serialVersionUID = 1L; // Keep same for compatibility
private String theme;
private int fontSize;
// Version 2 might add: private String language = "en";
// Version 3 might add: private boolean darkMode = false;
public AppSettings(String theme, int fontSize) {
this.theme = theme;
this.fontSize = fontSize;
}
@Override
public String toString() {
return String.format("Settings{theme='%s', fontSize=%d}", theme, fontSize);
}
}
public class VersionCompatibility {
public static void main(String[] args) throws IOException, ClassNotFoundException {
System.out.println("=== Version Compatibility ===\n");
// Simulate: Save with current version
AppSettings settings = new AppSettings("Material", 14);
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("settings.ser"))) {
oos.writeObject(settings);
System.out.println("Saved: " + settings);
}
// Load (works because serialVersionUID matches)
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("settings.ser"))) {
AppSettings loaded = (AppSettings) ois.readObject();
System.out.println("Loaded: " + loaded);
}
System.out.println("\n--- Class Evolution Rules ---");
System.out.println("Compatible changes (keep same serialVersionUID):");
System.out.println(" ✓ Adding new fields (get default values)");
System.out.println(" ✓ Adding writeObject/readObject methods");
System.out.println(" ✓ Changing access modifiers (private → public)");
System.out.println(" ✓ Adding classes to inheritance hierarchy");
System.out.println("");
System.out.println("Incompatible changes (MUST change serialVersionUID):");
System.out.println(" ✗ Removing fields");
System.out.println(" ✗ Changing field types (int → long)");
System.out.println(" ✗ Changing non-static to static");
System.out.println(" ✗ Changing Serializable to non-Serializable");
System.out.println(" ✗ Moving class to different package");
new File("settings.ser").delete();
}
}Security Risks of Deserialization
Deserialization is a known attack vector. Never deserialize untrusted data!
import java.io.*;
public class DeserializationSecurity {
public static void main(String[] args) {
System.out.println("=== Deserialization Security ===\n");
System.out.println("⚠️ DANGER: Deserializing untrusted data can lead to:");
System.out.println(" 1. Remote Code Execution (RCE)");
System.out.println(" 2. Denial of Service (DoS)");
System.out.println(" 3. Data tampering");
System.out.println(" 4. Privilege escalation");
System.out.println("\n--- How attacks work ---");
System.out.println(" Attacker crafts a malicious byte stream that,");
System.out.println(" when deserialized, triggers code execution through");
System.out.println(" 'gadget chains' in libraries on the classpath.");
System.out.println("\n--- Protection Measures ---");
System.out.println(" 1. Use ObjectInputFilter (Java 9+) to whitelist classes");
System.out.println(" 2. Never deserialize from untrusted sources");
System.out.println(" 3. Prefer JSON/XML over Java serialization for APIs");
System.out.println(" 4. Use look-ahead deserialization");
System.out.println(" 5. Keep libraries updated (patch gadget chains)");
// Java 9+ ObjectInputFilter example
System.out.println("\n--- ObjectInputFilter Example (Java 9+) ---");
demonstrateFilter();
}
static void demonstrateFilter() {
System.out.println(" // Only allow specific classes:");
System.out.println(" ObjectInputFilter filter = ObjectInputFilter.Config");
System.out.println(" .createFilter(\"com.myapp.*;!*\");");
System.out.println(" ois.setObjectInputFilter(filter);");
System.out.println(" ");
System.out.println(" // Limit object graph depth and size:");
System.out.println(" ObjectInputFilter filter = ObjectInputFilter.Config");
System.out.println(" .createFilter(\"maxdepth=5;maxbytes=1000000\");");
}
}Real-World Example: Application State Persistence
import java.io.*;
import java.time.*;
import java.util.*;
class AppState implements Serializable {
private static final long serialVersionUID = 2L;
private Map<String, String> preferences;
private List<String> recentFiles;
private LocalDateTime lastSaved;
private int windowWidth;
private int windowHeight;
public AppState() {
this.preferences = new HashMap<>();
this.recentFiles = new ArrayList<>();
this.windowWidth = 1024;
this.windowHeight = 768;
}
public void setPreference(String key, String value) {
preferences.put(key, value);
}
public void addRecentFile(String file) {
recentFiles.remove(file); // Remove if exists
recentFiles.add(0, file); // Add to front
if (recentFiles.size() > 10) {
recentFiles.remove(recentFiles.size() - 1);
}
}
public void setWindowSize(int w, int h) {
this.windowWidth = w;
this.windowHeight = h;
}
// Custom serialization to handle LocalDateTime (not always Serializable)
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
this.lastSaved = LocalDateTime.now();
oos.writeObject(lastSaved.toString());
}
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject();
String timeStr = (String) ois.readObject();
this.lastSaved = LocalDateTime.parse(timeStr);
}
public void display() {
System.out.println(" Window: " + windowWidth + "x" + windowHeight);
System.out.println(" Last saved: " + lastSaved);
System.out.println(" Preferences: " + preferences);
System.out.println(" Recent files: " + recentFiles);
}
// Save state to file
public static void save(AppState state, String filename) {
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(filename))) {
oos.writeObject(state);
} catch (IOException e) {
System.err.println("Failed to save state: " + e.getMessage());
}
}
// Load state from file (with fallback to defaults)
public static AppState load(String filename) {
File file = new File(filename);
if (!file.exists()) {
System.out.println(" No saved state found. Using defaults.");
return new AppState();
}
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(file))) {
return (AppState) ois.readObject();
} catch (Exception e) {
System.out.println(" Failed to load state: " + e.getMessage());
System.out.println(" Using defaults.");
return new AppState();
}
}
}
public class AppStatePersistence {
public static void main(String[] args) {
System.out.println("=== Application State Persistence ===\n");
// Simulate first run
System.out.println("--- First Run (creating state) ---");
AppState state = new AppState();
state.setPreference("theme", "dark");
state.setPreference("language", "en");
state.setPreference("autoSave", "true");
state.setWindowSize(1920, 1080);
state.addRecentFile("/docs/report.pdf");
state.addRecentFile("/code/Main.java");
state.addRecentFile("/data/sales.csv");
AppState.save(state, "appstate.ser");
System.out.println("State saved!\n");
// Simulate second run (loading state)
System.out.println("--- Second Run (loading state) ---");
AppState loaded = AppState.load("appstate.ser");
loaded.display();
new File("appstate.ser").delete();
}
}Output:
| Window | 1920x1080 |
| Last saved | 2024-01-15T14:30:45.123 |
| Preferences | {theme=dark, language=en, autoSave=true} |
| Recent files | [/data/sales.csv, /code/Main.java, /docs/report.pdf] |
Alternatives to Java Serialization
Interview Questions
Q1: What is deserialization and how does it reconstruct objects?
Answer: Deserialization reads a byte stream and reconstructs the Java object. It does NOT call the constructor — it uses internal JVM mechanisms to allocate memory and set field values directly from the stream. However, if a non-serializable parent class exists, its no-arg constructor IS called.
Q2: What exceptions can occur during deserialization?
Answer: (1) ClassNotFoundException — class doesn't exist in classpath, (2) InvalidClassException — serialVersionUID mismatch or class structure incompatible, (3) StreamCorruptedException — byte stream is corrupted or not serialized data, (4) EOFException — reached end of stream unexpectedly, (5) OptionalDataException — primitive data where object was expected.
Q3: Why is deserialization considered a security risk?
Answer: Deserializing untrusted data can trigger remote code execution. Attackers craft malicious byte streams that exploit "gadget chains" — sequences of method calls triggered during deserialization that ultimately execute arbitrary code. This has led to major vulnerabilities in Apache Commons, Spring, and other frameworks.
Q4: How do you safely deserialize in Java 9+?
Answer: Use ObjectInputFilter to whitelist allowed classes, limit object graph depth, and restrict byte size. Example: ois.setObjectInputFilter(ObjectInputFilter.Config.createFilter("com.myapp.*;maxdepth=5;!*")). The !* at the end rejects everything not explicitly allowed.
Q5: Does deserialization call the constructor?
Answer: No, not for the serializable class itself. The JVM allocates memory and sets fields directly. However, for the nearest NON-serializable superclass in the hierarchy, the no-arg constructor IS called. This is why non-serializable parents must have an accessible no-arg constructor.
Q6: What happens if you add a new field to a class after serialization?
Answer: If serialVersionUID matches (explicitly declared), deserialization succeeds — the new field gets its default value (null/0/false). If serialVersionUID was auto-generated (not declared), adding a field changes the computed UID, causing InvalidClassException.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Deserialization in Java.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Java Master Course topic.
Search Terms
java-master-course, java master course, java, master, course, file, handling, deserialization
Related Java Master Course Topics