Serializable Interface and serialVersionUID

Naveen AnamaneniUpdated · 5 min read

The Serializable interface in Java, in java.io, is a marker interface: it has no methods, it simply marks a class as allowed to be serialized. The serialVersionUID is a long number that identifies the version of a serializable class. When an object is read back, Java compares the number stored in the bytes with the number of the current class; if they differ, deserialization fails with InvalidClassException. In this lesson, you will learn both, and see what happens when a class changes after its objects were saved.

Declaration

Declaration
public interface Serializable { }
 
class Student implements Serializable {
    private static final long serialVersionUID = 1L;
}

Why serialVersionUID Matters

If a class doesn't declare serialVersionUID, Java computes one from the class's details: its name, fields, methods and more. Almost any change to the class, even adding a field, produces a different number, so objects saved earlier can no longer be read. Declaring the number yourself keeps it stable, so compatible changes such as adding a field still work.

SituationWithout a declared serialVersionUIDWith serialVersionUID = 1L
Add a fieldInvalidClassExceptionWorks; the new field gets its default value
Remove a fieldInvalidClassExceptionWorks; the old value is ignored
Change a field's typeInvalidClassExceptionInvalidClassException (incompatible)

The Checkpoint: Changing a Saved Class

This experiment uses three files. Save.java writes a Student, Load.java reads it back, and between the two runs the Student class is changed. Each program is compiled and run separately, so the output comes from real terminal runs.

Student.java
import java.io.Serializable;
 
public class Student implements Serializable {
    String name;
    int marks;
 
    Student(String name, int marks) {
        this.name = name;
        this.marks = marks;
    }
}
Save.java
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
 
public class Save {
    public static void main(String[] args) throws IOException {
        try (ObjectOutputStream out = new ObjectOutputStream(
                new FileOutputStream("student.ser"))) {
            out.writeObject(new Student("Navika", 91));
        }
        System.out.println("Saved");
    }
}
Load.java
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
 
public class Load {
    public static void main(String[] args)
            throws IOException, ClassNotFoundException {
        try (ObjectInputStream in = new ObjectInputStream(
                new FileInputStream("student.ser"))) {
            Student s = (Student) in.readObject();
            System.out.println("Loaded " + s.name + " " + s.marks);
        }
    }
}

First, save and load with the class unchanged:

Terminal
javac Student.java Save.java Load.java
java Save
java Load
Terminal output
Saved
Loaded Navika 91

Now add one field, String city;, to Student, recompile only Student.java, and load the old file again:

Terminal output
Exception in thread "main" java.io.InvalidClassException: Student; local class incompatible: stream classdesc serialVersionUID = -3428531181827717244, local class serialVersionUID = -8902825101767173921
    at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:496)
    ...

Explanation: The saved bytes carry the number Java computed for the old class. Adding city changed the computed number for the new class, so the two don't match and reading fails. Repeating the experiment with private static final long serialVersionUID = 1L; in Student from the start, the same change loads fine and prints Loaded Navika 91 null: the new city field simply gets its default value, null.

Rules of Serializable

  • A class is serializable if it, or any superclass, implements Serializable.
  • All fields that will be saved must hold serializable objects (or be transient).
  • Fields that are static or transient aren't saved.
  • Most JDK classes, such as String, wrappers, ArrayList, HashMap and LocalDate, are serializable.

Common Mistakes

Leaving out serialVersionUID. Every small change breaks old saved data.

Changing serialVersionUID for compatible changes. It makes old data unreadable for no reason.

Assuming Serializable has methods to implement. It's a marker with none.

Interview Corner

What is serialVersionUID, and why should you declare it?

serialVersionUID is a private static final long that identifies the version of a serializable class. During deserialization, Java compares the value stored in the stream with the value of the local class and throws InvalidClassException if they differ. If it isn't declared, Java computes it from the class structure, so almost any change, even adding a field, changes it and breaks compatibility with previously saved objects. Declaring it explicitly keeps compatible changes working.

Practice

  1. Easy: Add serialVersionUID to a serializable class of your own and annotate it with @Serial.
  2. Harder: Repeat the checkpoint, but change marks from int to String while keeping serialVersionUID = 1L. What exception do you get, and why?

Quick check: Serializable and serialVersionUID

4 questions
1How many methods does Serializable have?
2What happens if serialVersionUID values don't match?
3With serialVersionUID = 1L, what happens when a field is added?
4How is serialVersionUID declared?

Key Takeaways

Was this tutorial helpful?

Lesson 371 of 482