Dev Learning Lab

The labs on this site need JavaScript to run. The same content is written out below.

Learn › Java › I/O

Serialization

What comes back when you restore a saved object?

The default serialization mechanism in Java converts an object to bytes and writes them to a file (writeObject), and then creates the object again from those bytes (readObject). But not everything in an object is saved, and not every object can be saved. Set point to 50, save the object, change point to 70, and then restore the object to see what is saved and what is not.

Checked against real execution · The 15 combinations were compiled and run on JDK 21 and checked against the results

Concepts in this lab

Serialization and deserialization
Serialization converts an object into a form that can be stored or sent (such as a sequence of bytes or JSON), and deserialization restores an object from that form. In web APIs, the typical case is conversion between objects and JSON (for example with Jackson). Java's built-in serialization (Serializable) has compatibility problems when the class structure changes and carries security risks, so formats such as JSON are mostly used today.
Serializable
This is the interface that marks a class as one whose objects can be saved with the default serialization mechanism in Java. It has no methods to implement, and adding implements Serializable to the class declaration is all it takes to mark the class. If you try to write an object that does not have this mark with ObjectOutputStream, a NotSerializableException is thrown. When an object is saved, the objects that its fields refer to are saved with it, so the classes of those objects must all be Serializable as well.
transient
This is the keyword that marks a field as one that must not be saved during serialization. A field marked with it is not included in the bytes that go out to a file or the network, and in the restored object it has the default value of its type (0 for a number, null for a reference type). It is used for values that must not be left outside the program, such as a password, for values that can simply be computed again, and for fields that refer to an object that cannot be saved because it is not Serializable. Your code has to take into account that the field is empty after the object is restored.
serialVersionUID
This is a number that indicates which version of a class a piece of serialized data was created with. The number is recorded together with the object when the object is saved, and if it differs from the number of the current class when the object is restored, an InvalidClassException is thrown. If you do not declare it yourself in the class, as in private static final long serialVersionUID = 1L;, Java computes a value from the name, fields, methods, and other details of the class, and even a small change to the class can change this value. For this reason, it is recommended that a Serializable class declare this number explicitly.
static
This keyword marks a member as belonging to the class, not to an instance. A static field exists only once per class and is shared by all instances, and a static method is called without creating an object. A static field whose value changes is effectively a global variable, so it easily causes problems when several threads access it at the same time.

Try these

Running with the default settings

  1. new User("kim"): This creates a User object. The constructor runs, and "constructor ran" is printed once.
  2. u.point = 50: This puts 50 into the point field of the object u.
  3. out.writeObject(u): This converts the field values of the object u to bytes and writes them to the file. Only name = "kim" is saved. point is declared transient, so it is skipped. So that the class of the object can be identified, the class name User and the serialVersionUID (1) are recorded as well.
  4. u.point = 70: After saving, the value in memory is changed to 70. The contents of the file that was already written do not change.
  5. in.readObject(): This checks whether the class name and the serialVersionUID recorded in the file are the same as those of the current User class. They are the same, so it creates a new User object and fills in the saved field values. The constructor of User does not run at this point. "constructor ran" is not printed again.
  6. r.point: It is 0. The transient field was not saved in the file, so there is no value to fill in. It gets the default value of its type (0 for int, null for a reference type).

Result of this run

Code

class User implements Serializable {
    private static final long serialVersionUID = 1L;
    String name;
    transient int point;                          // transient: not saved
    User(String name) {
        this.name = name;
        System.out.println("constructor ran");
    }
}

User u = new User("kim");
u.point = 50;
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.ser"))) {
    out.writeObject(u);                           // convert the object to bytes and write them to the file
}
u.point = 70;                                     // change the value after saving

try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.ser"))) {
    User r = (User) in.readObject();              // restore the object from the bytes in the file
    System.out.println(r.name + " " + r.point);
}

Assumptions of this model

For each of the 15 combinations, source code identical to the code on the screen was compiled and run on JDK 21 on this PC, and whether saving and restoring succeeded, the exception that was thrown, the restored value, and the number of times the constructor ran were checked against the model. For the combinations that modify the class after saving, two separate copies of a User class with the same name were compiled, and a file saved with one was read with the other. The following were also confirmed by separate runs: when a field is added to a class that declares serialVersionUID, the file can be read and the new field is null, a transient field of a reference type becomes null, a static field read from a different program is 0, and the object is saved when the Address field is marked transient. The format of the bytes that are actually recorded in the file is simplified in the diagram. That the constructor does not run applies to a class that implements Serializable. The rules for the case where the superclass is not Serializable, how to define writeObject and readObject yourself, and the serialization of records are not covered. Because of security problems, the default serialization mechanism in Java is rarely used in new code, and formats such as JSON are mostly used, but you need to know these rules to understand existing code and libraries.

Previous lab: I/O streams and buffers

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these