Object Serialization in Java: A Complete Guide with Examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

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

Recommendations