Backend Development 16 min read

Master Java Serialization: Common Pitfalls and Best Practices

This article explains the fundamentals of Java serialization and deserialization, compares various serialization formats, demonstrates code examples, highlights common issues such as static, transient fields and serialVersionUID mismatches, and shows how to implement custom serialization with the Externalizable interface.

macrozheng
macrozheng
macrozheng
Master Java Serialization: Common Pitfalls and Best Practices

01. Background Introduction

Serialization and Deserialization are tasks engineers face daily, especially in micro‑service development. For beginners the term can be abstract, so think of it like magic: turning an object into a stream of bytes (serialization) and turning that stream back into an object (deserialization).

From a programming perspective, serialization converts an object into a byte stream that any computer can understand; deserialization restores the byte stream into a program‑recognizable object. The ultimate goal is to enable cross‑platform storage and network transmission of data.

Any data that requires cross‑platform storage or network transmission must be serialized.

Early Internet serialization methods included COM and CORBA. COM was Windows‑only and tightly coupled to the compiler, while CORBA supported cross‑platform and cross‑language communication but suffered from versioning and complexity issues. Later, formats such as XML, JSON, Protobuf, Thrift, and Avro became popular.

Choosing a serialization format depends on factors such as cross‑platform support, serialization speed, and the size of the serialized data.

02. Code Practice

In Java, implementing serialization is as simple as implementing the

Serializable

interface:

<code>public class Student implements Serializable {
    /** username */
    private String name;
    /** age */
    private Integer age;
    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{name='" + name + "', age=" + age + "}";
    }
}</code>

Testing serialization and deserialization:

<code>public class ObjectMainTest {
    public static void main(String[] args) throws Exception {
        serialize();
        deserialize();
    }
    private static void serialize() throws Exception {
        Student s = new Student("张三", 20);
        System.out.println(s);
        System.out.println("=== Start Serialization ===");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.log"));
        oos.writeObject(s);
        oos.close();
    }
    private static void deserialize() throws Exception {
        System.out.println("=== Start Deserialization ===");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.log"));
        Student s = (Student) ois.readObject();
        ois.close();
        System.out.println(s);
    }
}</code>

Output shows successful round‑trip serialization.

03. Serialization Issues Summary

3.1 Static fields are not serialized

Fields marked with

static

belong to the class, not the instance, so they are not included in the serialized byte stream.

3.2 Transient fields are not serialized

Fields marked with

transient

are intentionally omitted from serialization. For example, making

name

transient results in a

null

value after deserialization.

3.3 serialVersionUID mismatches

Every

Serializable

class has a version identifier. If you do not define it, the JDK generates one based on the class structure. Changing the class (e.g., adding a field) changes the generated UID, causing

InvalidClassException

during deserialization. Defining a constant

private static final long serialVersionUID = 1L;

prevents this problem.

3.4 Parent‑child serialization

If a parent class does not implement

Serializable

but the child does, the parent’s fields are not serialized. If the parent implements

Serializable

, its fields are serialized regardless of whether the child implements the interface.

3.5 Custom serialization

For fine‑grained control, implement

Externalizable

, which requires

writeExternal(ObjectOutput)

and

readExternal(ObjectInput)

methods.

<code>public class Person implements Externalizable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    public Person() { System.out.println("Person: empty"); }
    public Person(String name, int age) { this.name = name; this.age = age; }
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("person writeExternal...");
        out.writeObject(name);
        out.writeInt(age);
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        System.out.println("person readExternal...");
        name = (String) in.readObject();
        age = in.readInt();
    }
    @Override
    public String toString() { return "Person{name='" + name + "', age=" + age + "}"; }
}</code>

Testing custom serialization:

<code>public class ExternalizableMain {
    public static void main(String[] args) throws Exception {
        Person p = new Person("张三", 15);
        System.out.println(p);
        System.out.println("=== Start Serialization ===");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.log"));
        oos.writeObject(p);
        oos.close();
        System.out.println("=== Start Deserialization ===");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.log"));
        Person p2 = (Person) ois.readObject();
        System.out.println(p2);
    }
}</code>

The output demonstrates the custom write and read methods being invoked.

04. Summary

Serialization is ubiquitous in backend development, especially in micro‑service frameworks such as Spring Boot + Dubbo. Forgetting to define

serialVersionUID

or overlooking static/transient fields can cause runtime failures. Defining a stable version UID and understanding inheritance rules are essential for reliable data exchange.

BackendJavamicroservicesSerializationdeserializationserialVersionUIDExternalizable
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.