Object Serialization in Java: From Basics to Advanced Techniques
Serialization is the process of converting an object's state into a byte stream for saving (to a file, database) or transmitting over a network. The reverse process — deserialization — restores the object from the byte stream. In Java, this mechanism is built into the platform and is widely used in distributed systems, caching, and web applications. In this article, we will explore how serialization works, which interfaces and classes are involved, and how to properly implement custom serialization.
Basics: The Serializable Interface
For an object to be serializable, its class must implement the marker interface java.io.Serializable. This interface contains no methods — it simply tells the virtual machine that the object is allowed to be serialized.
import java.io.Serializable;
public class User implements Serializable { private static final long serialVersionUID = 1L; private String name; private int age;
public User(String name, int age) { this.name = name; this.age = age; }
@Override public String toString() { return "User{name='" + name + "', age=" + age + "}"; }}The serialVersionUID field is a unique identifier for the class version. If not declared explicitly, the JVM will compute it automatically, but this can lead to an InvalidClassException during deserialization if the class has changed. It is recommended to always define it manually.
The Serialization and Deserialization Process
To write an object to a stream, ObjectOutputStream is used; to read it, ObjectInputStream is used. Let's look at a basic example:
import java.io.*;
public class SerializationDemo { public static void main(String[] args) { User user = new User("Alexey", 30);
// Serialization try (FileOutputStream fos = new FileOutputStream("user.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos)) { oos.writeObject(user); System.out.println("Object serialized: " + user); } catch (IOException e) { e.printStackTrace(); }
// Deserialization try (FileInputStream fis = new FileInputStream("user.ser"); ObjectInputStream ois = new ObjectInputStream(fis)) { User restoredUser = (User) ois.readObject(); System.out.println("Object restored: " + restoredUser); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } }}During deserialization, the object's constructor is not called — the state is restored directly from the stream. This is an important difference from normal object creation using new.
The transient Keyword
Sometimes you need to exclude certain fields from serialization, such as passwords or temporary calculations. For this, the transient modifier is used. Fields marked with it receive default values (0, null, false) during serialization.
public class UserWithPassword implements Serializable { private static final long serialVersionUID = 1L; private String login; private transient String password; // will not be serialized
public UserWithPassword(String login, String password) { this.login = login; this.password = password; }
@Override public String toString() { return "User{login='" + login + "', password='" + password + "'}"; }}After deserialization, the password field will be null. This is convenient for security but requires additional logic for restoration.
Custom Serialization: writeObject and readObject Methods
If the default behavior is not suitable, you can override the private methods
Also in library
Working with XML in Kotlin: Libraries, Parsing, and Document Creation
JWT Tokens in Swift: Working with JSON Web Token on iOS/macOS
Working with CSV in Swift (iOS/macOS): SwiftCSV Library and Parsing Basics
RapidJSON for C++: Complete Guide with Examples
Working with S3 and Cloud Storage in C++: AWS SDK for C++ Library