Java Serialization

Java Serialization is a mechanism that allows objects to be converted into a byte stream for storage or transmission, and then reconstructed back into objects later.

1. Introduction to Java Serialization

What is Serialization?

  • Serialization: Converting an object into a byte stream
  • Deserialization: Reconstructing an object from a byte stream
  • Uses: Persistence, RMI, caching, network transmission

Key Interfaces and Classes

  • java.io.Serializable - Marker interface for serializable classes
  • java.io.Externalizable - Interface for custom serialization
  • ObjectOutputStream - Writes objects to output stream
  • ObjectInputStream - Reads objects from input stream

2. Basic Serialization

Simple Serializable Class

import java.io.Serializable;
import java.util.Date;
// Implementing Serializable makes objects serializable
public class Person implements Serializable {
// serialVersionUID for version control
private static final long serialVersionUID = 1L;
private String name;
private int age;
private transient String password; // transient fields are not serialized
private Date birthDate;
// Constructors
public Person() {}
public Person(String name, int age, String password, Date birthDate) {
this.name = name;
this.age = age;
this.password = password;
this.birthDate = birthDate;
}
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public Date getBirthDate() { return birthDate; }
public void setBirthDate(Date birthDate) { this.birthDate = birthDate; }
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + 
", password='" + password + "', birthDate=" + birthDate + "}";
}
}

Basic Serialization and Deserialization

import java.io.*;
import java.util.Date;
public class BasicSerializationExample {
public static void main(String[] args) {
String filename = "person.ser";
// Create a Person object
Person person = new Person("John Doe", 30, "secret123", new Date());
System.out.println("Original: " + person);
// Serialize the object
serializeObject(person, filename);
// Deserialize the object
Person deserializedPerson = deserializeObject(filename);
System.out.println("Deserialized: " + deserializedPerson);
}
public static void serializeObject(Serializable obj, String filename) {
try (FileOutputStream fileOut = new FileOutputStream(filename);
ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
out.writeObject(obj);
System.out.println("Object serialized successfully to " + filename);
} catch (IOException e) {
e.printStackTrace();
}
}
public static <T> T deserializeObject(String filename) {
try (FileInputStream fileIn = new FileInputStream(filename);
ObjectInputStream in = new ObjectInputStream(fileIn)) {
@SuppressWarnings("unchecked")
T obj = (T) in.readObject();
System.out.println("Object deserialized successfully from " + filename);
return obj;
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}

3. Advanced Serialization Features

Custom Serialization with readObject and writeObject

import java.io.*;
public class User implements Serializable {
private static final long serialVersionUID = 2L;
private String username;
private transient String password; // Won't be serialized normally
private transient String email;
private int loginCount;
public User(String username, String password, String email) {
this.username = username;
this.password = password;
this.email = email;
this.loginCount = 0;
}
// Custom serialization logic
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // Serialize non-transient fields
// Custom serialization for transient fields
out.writeObject(encrypt(password)); // Encrypt before serializing
out.writeObject(email);
System.out.println("Custom writeObject called");
}
// Custom deserialization logic
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // Deserialize non-transient fields
// Custom deserialization for transient fields
this.password = decrypt((String) in.readObject());
this.email = (String) in.readObject();
System.out.println("Custom readObject called");
}
// Simple encryption/decryption for demo (not secure for production)
private String encrypt(String data) {
// Simple Caesar cipher for demonstration
StringBuilder encrypted = new StringBuilder();
for (char c : data.toCharArray()) {
encrypted.append((char) (c + 1));
}
return encrypted.toString();
}
private String decrypt(String data) {
StringBuilder decrypted = new StringBuilder();
for (char c : data.toCharArray()) {
decrypted.append((char) (c - 1));
}
return decrypted.toString();
}
// Getters and setters
public String getUsername() { return username; }
public String getPassword() { return password; }
public String getEmail() { return email; }
public int getLoginCount() { return loginCount; }
public void incrementLoginCount() { loginCount++; }
@Override
public String toString() {
return "User{username='" + username + "', password='" + password + 
"', email='" + email + "', loginCount=" + loginCount + "}";
}
}

Testing Custom Serialization

public class CustomSerializationExample {
public static void main(String[] args) {
String filename = "user.ser";
// Create user
User user = new User("johndoe", "mypassword", "[email protected]");
user.incrementLoginCount();
user.incrementLoginCount();
System.out.println("Original: " + user);
// Serialize
BasicSerializationExample.serializeObject(user, filename);
// Deserialize
User deserializedUser = BasicSerializationExample.deserializeObject(filename);
System.out.println("Deserialized: " + deserializedUser);
}
}

4. Externalizable Interface

Custom Serialization with Externalizable

import java.io.*;
public class Product implements Externalizable {
private static final long serialVersionUID = 3L;
private String name;
private double price;
private int quantity;
private transient String internalCode; // Not automatically handled
// Required no-arg constructor for Externalizable
public Product() {
System.out.println("No-arg constructor called");
}
public Product(String name, double price, int quantity, String internalCode) {
this.name = name;
this.price = price;
this.quantity = quantity;
this.internalCode = internalCode;
}
// Custom serialization - full control
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(name);
out.writeDouble(price);
out.writeInt(quantity);
out.writeUTF(internalCode != null ? internalCode : "DEFAULT");
System.out.println("writeExternal called");
}
// Custom deserialization - full control
@Override
public void readExternal(ObjectInput in) throws IOException {
this.name = in.readUTF();
this.price = in.readDouble();
this.quantity = in.readInt();
this.internalCode = in.readUTF();
System.out.println("readExternal called");
}
// Getters and setters
public String getName() { return name; }
public double getPrice() { return price; }
public int getQuantity() { return quantity; }
public String getInternalCode() { return internalCode; }
@Override
public String toString() {
return "Product{name='" + name + "', price=" + price + 
", quantity=" + quantity + ", internalCode='" + internalCode + "'}";
}
}

Testing Externalizable

public class ExternalizableExample {
public static void main(String[] args) {
String filename = "product.ser";
Product product = new Product("Laptop", 999.99, 5, "LAP-001");
System.out.println("Original: " + product);
// Serialize
serializeExternalizable(product, filename);
// Deserialize
Product deserializedProduct = deserializeExternalizable(filename);
System.out.println("Deserialized: " + deserializedProduct);
}
public static void serializeExternalizable(Externalizable obj, String filename) {
try (FileOutputStream fileOut = new FileOutputStream(filename);
ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
obj.writeExternal(out);
System.out.println("Externalizable object serialized to " + filename);
} catch (IOException e) {
e.printStackTrace();
}
}
public static <T extends Externalizable> T deserializeExternalizable(String filename) {
try (FileInputStream fileIn = new FileInputStream(filename);
ObjectInputStream in = new ObjectInputStream(fileIn)) {
@SuppressWarnings("unchecked")
T obj = (T) in.readObject();
return obj;
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}

5. Inheritance and Serialization

Serialization with Inheritance

import java.io.*;
// Base class
class Animal implements Serializable {
private static final long serialVersionUID = 1L;
private String species;
private int age;
public Animal() {
System.out.println("Animal no-arg constructor");
}
public Animal(String species, int age) {
this.species = species;
this.age = age;
}
public String getSpecies() { return species; }
public int getAge() { return age; }
@Override
public String toString() {
return "Animal{species='" + species + "', age=" + age + "}";
}
}
// Derived class
class Dog extends Animal {
private static final long serialVersionUID = 2L;
private String breed;
private transient String collarId; // transient field
public Dog() {
System.out.println("Dog no-arg constructor");
}
public Dog(String species, int age, String breed, String collarId) {
super(species, age);
this.breed = breed;
this.collarId = collarId;
}
// Custom serialization for Dog
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // Serialize Dog's fields
// Handle transient field
out.writeObject(collarId != null ? collarId : "UNKNOWN");
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // Deserialize Dog's fields
// Handle transient field
this.collarId = (String) in.readObject();
}
public String getBreed() { return breed; }
public String getCollarId() { return collarId; }
@Override
public String toString() {
return "Dog{" + super.toString() + ", breed='" + breed + 
"', collarId='" + collarId + "'}";
}
}
// Testing inheritance serialization
public class InheritanceSerializationExample {
public static void main(String[] args) {
String filename = "dog.ser";
Dog dog = new Dog("Canine", 3, "Golden Retriever", "COL-123");
System.out.println("Original: " + dog);
// Serialize
BasicSerializationExample.serializeObject(dog, filename);
// Deserialize
Dog deserializedDog = BasicSerializationExample.deserializeObject(filename);
System.out.println("Deserialized: " + deserializedDog);
}
}

6. Collection Serialization

Serializing Collections

import java.io.*;
import java.util.*;
public class CollectionSerializationExample {
public static void main(String[] args) {
String filename = "collection.ser";
// Create complex object with collections
Library library = new Library("City Library");
library.addBook(new Book("1984", "George Orwell", 1949));
library.addBook(new Book("To Kill a Mockingbird", "Harper Lee", 1960));
library.addMember(new Member("Alice", "[email protected]"));
library.addMember(new Member("Bob", "[email protected]"));
System.out.println("Original Library:");
System.out.println(library);
// Serialize
serializeLibrary(library, filename);
// Deserialize
Library deserializedLibrary = deserializeLibrary(filename);
System.out.println("Deserialized Library:");
System.out.println(deserializedLibrary);
}
public static void serializeLibrary(Library library, String filename) {
try (FileOutputStream fileOut = new FileOutputStream(filename);
ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
out.writeObject(library);
System.out.println("Library serialized to " + filename);
} catch (IOException e) {
e.printStackTrace();
}
}
public static Library deserializeLibrary(String filename) {
try (FileInputStream fileIn = new FileInputStream(filename);
ObjectInputStream in = new ObjectInputStream(fileIn)) {
return (Library) in.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}
class Library implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private List<Book> books;
private Set<Member> members;
private transient int accessCount; // Not serialized
public Library(String name) {
this.name = name;
this.books = new ArrayList<>();
this.members = new HashSet<>();
this.accessCount = 0;
}
public void addBook(Book book) {
books.add(book);
accessCount++;
}
public void addMember(Member member) {
members.add(member);
accessCount++;
}
// Custom serialization
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeInt(accessCount); // Manually serialize transient field
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
this.accessCount = in.readInt(); // Manually deserialize transient field
}
@Override
public String toString() {
return "Library{name='" + name + "', books=" + books + 
", members=" + members + ", accessCount=" + accessCount + "}";
}
}
class Book implements Serializable {
private static final long serialVersionUID = 1L;
private String title;
private String author;
private int publicationYear;
public Book(String title, String author, int publicationYear) {
this.title = title;
this.author = author;
this.publicationYear = publicationYear;
}
// Getters
public String getTitle() { return title; }
public String getAuthor() { return author; }
public int getPublicationYear() { return publicationYear; }
@Override
public String toString() {
return title + " by " + author + " (" + publicationYear + ")";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Book)) return false;
Book book = (Book) o;
return publicationYear == book.publicationYear &&
Objects.equals(title, book.title) &&
Objects.equals(author, book.author);
}
@Override
public int hashCode() {
return Objects.hash(title, author, publicationYear);
}
}
class Member implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String email;
public Member(String name, String email) {
this.name = name;
this.email = email;
}
// Getters
public String getName() { return name; }
public String getEmail() { return email; }
@Override
public String toString() {
return name + " (" + email + ")";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Member)) return false;
Member member = (Member) o;
return Objects.equals(email, member.email);
}
@Override
public int hashCode() {
return Objects.hash(email);
}
}

7. Version Control with serialVersionUID

Handling Class Evolution

import java.io.*;
// Version 1.0 of the class
class EmployeeV1 implements Serializable {
private static final long serialVersionUID = 1L; // Explicit version UID
private String name;
private int employeeId;
private double salary;
public EmployeeV1(String name, int employeeId, double salary) {
this.name = name;
this.employeeId = employeeId;
this.salary = salary;
}
// Getters
public String getName() { return name; }
public int getEmployeeId() { return employeeId; }
public double getSalary() { return salary; }
@Override
public String toString() {
return "EmployeeV1{name='" + name + "', employeeId=" + employeeId + 
", salary=" + salary + "}";
}
}
// Version 2.0 of the class - added department field
class EmployeeV2 implements Serializable {
private static final long serialVersionUID = 1L; // Same UID - compatible change
private String name;
private int employeeId;
private double salary;
private String department; // New field
public EmployeeV2(String name, int employeeId, double salary, String department) {
this.name = name;
this.employeeId = employeeId;
this.salary = salary;
this.department = department;
}
// Getters
public String getName() { return name; }
public int getEmployeeId() { return employeeId; }
public double getSalary() { return salary; }
public String getDepartment() { return department; }
@Override
public String toString() {
return "EmployeeV2{name='" + name + "', employeeId=" + employeeId + 
", salary=" + salary + ", department='" + department + "'}";
}
}
// Version 3.0 - incompatible change (different UID)
class EmployeeV3 implements Serializable {
private static final long serialVersionUID = 2L; // Different UID - incompatible
private String fullName; // Changed field name
private String employeeCode; // Changed field type and name
private double monthlySalary; // Changed field name
private String department;
public EmployeeV3(String fullName, String employeeCode, double monthlySalary, String department) {
this.fullName = fullName;
this.employeeCode = employeeCode;
this.monthlySalary = monthlySalary;
this.department = department;
}
@Override
public String toString() {
return "EmployeeV3{fullName='" + fullName + "', employeeCode='" + employeeCode + 
"', monthlySalary=" + monthlySalary + ", department='" + department + "'}";
}
}
public class VersionControlExample {
public static void main(String[] args) {
demonstrateCompatibleChanges();
demonstrateIncompatibleChanges();
}
public static void demonstrateCompatibleChanges() {
System.out.println("=== Compatible Changes (Same serialVersionUID) ===");
String filename = "employeev1.ser";
// Create and serialize V1
EmployeeV1 v1 = new EmployeeV1("John Doe", 1001, 50000.0);
System.out.println("Original V1: " + v1);
BasicSerializationExample.serializeObject(v1, filename);
// Try to deserialize as V2 (compatible)
try (FileInputStream fileIn = new FileInputStream(filename);
ObjectInputStream in = new ObjectInputStream(fileIn)) {
EmployeeV2 v2 = (EmployeeV2) in.readObject();
System.out.println("Deserialized as V2: " + v2);
System.out.println("Department: " + v2.getDepartment()); // Will be null
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void demonstrateIncompatibleChanges() {
System.out.println("\n=== Incompatible Changes (Different serialVersionUID) ===");
String filename = "employeev1.ser";
// Try to deserialize V1 as V3 (incompatible)
try (FileInputStream fileIn = new FileInputStream(filename);
ObjectInputStream in = new ObjectInputStream(fileIn)) {
EmployeeV3 v3 = (EmployeeV3) in.readObject();
System.out.println("Deserialized as V3: " + v3);
} catch (InvalidClassException e) {
System.out.println("InvalidClassException: " + e.getMessage());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}

8. Security Considerations

Secure Serialization Practices

import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class SecureSerialization {
// Validating deserialized objects
public static <T> T safeDeserialize(String filename, Class<T> expectedClass) 
throws IOException, ClassNotFoundException {
try (FileInputStream fileIn = new FileInputStream(filename);
ObjectInputStream in = new ObjectInputStream(fileIn)) {
Object obj = in.readObject();
// Validate the deserialized object
if (!expectedClass.isInstance(obj)) {
throw new SecurityException("Deserialized object is not of expected type: " + 
obj.getClass().getName());
}
// Additional validation based on business rules
if (obj instanceof Validatable) {
Validatable validatable = (Validatable) obj;
if (!validatable.isValid()) {
throw new SecurityException("Deserialized object failed validation");
}
}
return expectedClass.cast(obj);
}
}
// Using object filtering (Java 9+)
public static class ObjectInputFilterExample {
public static <T> T deserializeWithFilter(String filename, Class<T> expectedClass) 
throws IOException, ClassNotFoundException {
try (FileInputStream fileIn = new FileInputStream(filename);
ObjectInputStream in = new ObjectInputStream(fileIn)) {
// Set filter to allow only specific classes
ObjectInputFilter filter = ObjectInputFilter.allowFilter(
clazz -> expectedClass.equals(clazz), 
ObjectInputFilter.Status.REJECTED
);
in.setObjectInputFilter(filter);
return expectedClass.cast(in.readObject());
}
}
}
}
interface Validatable {
boolean isValid();
}
class SecurePerson implements Serializable, Validatable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private transient String validationToken;
public SecurePerson(String name, int age) {
this.name = name;
this.age = age;
this.validationToken = generateValidationToken();
}
private String generateValidationToken() {
return "VALID_" + System.currentTimeMillis();
}
@Override
public boolean isValid() {
// Custom validation logic
return name != null && !name.trim().isEmpty() && 
age >= 0 && age <= 150 &&
validationToken != null && validationToken.startsWith("VALID_");
}
// Custom serialization to include validation
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeObject(validationToken);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
this.validationToken = (String) in.readObject();
}
@Override
public String toString() {
return "SecurePerson{name='" + name + "', age=" + age + "}";
}
}

9. Serialization Best Practices

Best Practices and Common Pitfalls

import java.io.*;
import java.util.*;
public class SerializationBestPractices {
/*
BEST PRACTICES:
1. Always declare serialVersionUID
2. Mark sensitive fields as transient
3. Use custom serialization for complex objects
4. Validate deserialized objects
5. Consider using Externalizable for performance
6. Be cautious with inheritance
7. Handle version compatibility
8. Use object validation
*/
public static class WellDesignedSerializableClass implements Serializable {
private static final long serialVersionUID = 1L;
private final String id; // Final fields work with serialization
private String name;
private transient String sensitiveData; // Sensitive data not serialized
private List<String> items;
public WellDesignedSerializableClass(String id, String name) {
this.id = id;
this.name = name;
this.sensitiveData = "Confidential";
this.items = new ArrayList<>();
}
// Custom serialization
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
// Encrypt sensitive data if needed
out.writeObject(encrypt(sensitiveData));
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
// Decrypt sensitive data
this.sensitiveData = decrypt((String) in.readObject());
// Initialize transient fields if needed
if (this.items == null) {
this.items = new ArrayList<>();
}
}
private String encrypt(String data) {
// Simple encryption for demo
return new StringBuilder(data).reverse().toString();
}
private String decrypt(String data) {
return new StringBuilder(data).reverse().toString();
}
// Getters
public String getId() { return id; }
public String getName() { return name; }
public String getSensitiveData() { return sensitiveData; }
public List<String> getItems() { return items; }
public void addItem(String item) {
items.add(item);
}
@Override
public String toString() {
return "WellDesignedSerializableClass{id='" + id + "', name='" + name + 
"', sensitiveData='" + sensitiveData + "', items=" + items + "}";
}
}
public static void demonstrateBestPractices() {
String filename = "best_practice.ser";
WellDesignedSerializableClass obj = new WellDesignedSerializableClass("001", "Test Object");
obj.addItem("Item1");
obj.addItem("Item2");
System.out.println("Original: " + obj);
// Serialize
BasicSerializationExample.serializeObject(obj, filename);
// Deserialize with validation
try {
WellDesignedSerializableClass deserialized = 
SecureSerialization.safeDeserialize(filename, WellDesignedSerializableClass.class);
System.out.println("Deserialized: " + deserialized);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
demonstrateBestPractices();
}
}

10. Alternative Serialization Approaches

JSON Serialization with Jackson (Alternative)

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.util.List;
import java.util.ArrayList;
public class JsonSerializationExample {
public static class JsonPerson {
private String name;
private int age;
@JsonIgnore // Similar to transient
private String password;
private List<String> hobbies;
// Required no-arg constructor for Jackson
public JsonPerson() {
this.hobbies = new ArrayList<>();
}
public JsonPerson(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
this.hobbies = new ArrayList<>();
}
// Getters and setters (required for Jackson)
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public List<String> getHobbies() { return hobbies; }
public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; }
public void addHobby(String hobby) {
hobbies.add(hobby);
}
@Override
public String toString() {
return "JsonPerson{name='" + name + "', age=" + age + 
", password='" + password + "', hobbies=" + hobbies + "}";
}
}
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
JsonPerson person = new JsonPerson("John", 30, "secret");
person.addHobby("Reading");
person.addHobby("Swimming");
// Serialize to JSON
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(person);
System.out.println("JSON Serialized:");
System.out.println(json);
// Deserialize from JSON
JsonPerson deserializedPerson = mapper.readValue(json, JsonPerson.class);
System.out.println("\nJSON Deserialized:");
System.out.println(deserializedPerson);
}
}

Summary

Key Java Serialization Concepts:

  1. Serializable Interface: Marker interface for serializable classes
  2. serialVersionUID: Version control for class evolution
  3. transient keyword: Excludes fields from serialization
  4. Custom Serialization: readObject() and writeObject() methods
  5. Externalizable: Full control over serialization process
  6. Inheritance: Parent classes must be serializable
  7. Security: Validate deserialized objects, use filters

Best Practices:

  • Always declare serialVersionUID
  • Mark sensitive fields as transient
  • Use custom serialization for complex logic
  • Validate deserialized objects
  • Consider security implications
  • Handle version compatibility

Alternatives:

  • JSON serialization (Jackson, Gson)
  • XML serialization (JAXB)
  • Protocol Buffers
  • Apache Avro

Java Serialization is powerful but should be used carefully due to security concerns and versioning issues. Consider alternatives like JSON for cross-platform compatibility.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper