Topic 107 of 119 14. Additional Java Topics Interactive Tutorial

Java Serialization: Object Persistence, transient Keyword, and serialVersionUID

10 mins read Last updated: 22 Aug 2026 15,511 views

Direct Answer & Core Concept

Java Serialization allows an object to be converted into a byte stream so that its state can be stored or transferred. Later, that byte stream can be converted back into an object through a process called deserialization.

Serialization is useful when an application needs to persist object data, transfer objects between systems, or temporarily store an object's state. Java provides built-in support through Serializable, ObjectOutputStream, and ObjectInputStream.

In this tutorial, you will learn what Java serialization is, how serialization and deserialization work, how the transient keyword affects object fields, and why serialVersionUID is important for version compatibility.

Detailed Explanation & Mechanics

What Is Serialization in Java?

Serialization is the process of converting a Java object's state into a sequence of bytes. The resulting byte stream can be written to a file, database, network connection, or another storage mechanism.

The class whose objects need to be serialized generally implements the Serializable interface. Serializable is a marker interface, which means it does not require you to implement any methods.

Basic example

Main.java
Run Online
import java.io.Serializable;
class Student implements Serializable {
    String name;
    int age;
}

Because Student implements Serializable, its objects can be written using Java's object output streams.


Serialization vs Deserialization

These two operations work in opposite directions.

Serialization:

Main.java
Run Online
Java Object↓Byte Stream↓File / Network / Storage

Deserialization:

Main.java
Run Online
File / Network / Storage↓Byte Stream↓Java Object

Serialization stores the object's state in a byte representation, while deserialization reconstructs an object from that representation.


How Java Serialization Works

Java provides two important classes for basic object serialization:

  • ObjectOutputStream — writes objects to an output stream.

  • ObjectInputStream — reads objects from an input stream.

A typical serialization process looks like this:

Main.java
Run Online
ObjectOutputStream↓writeObject()↓Object↓Byte Stream↓File

During deserialization:

Main.java
Run Online
File↓ObjectInputStream↓readObject()↓Object

The Serializable Interface

The Serializable interface belongs to the java.io package.

Main.java
Run Online
import java.io.Serializable;
class Employee implements Serializable {
    private String name;
    private double salary;
}

Serializable is a marker interface. It tells Java that instances of the class are eligible for the default serialization mechanism.

It does not contain a method such as serialize() that you have to implement.

Why is it called a marker interface?

A marker interface provides metadata about a class rather than defining behavior.

Examples include:

When Java sees that a class implements Serializable, its object can participate in Java's standard object serialization mechanism.


Simple Java Serialization Example

Consider an Employee class:

Main.java
Run Online
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class Employee implements Serializable {
    private String name;
    private int id;
    public Employee(String name, int id) {
        this.name = name;
        this.id = id;
    }
}public class Main {
    public static void main(String[] args) {
        Employee employee = new Employee("Rahul", 101);
        try(ObjectOutputStream output = new ObjectOutputStream( new FileOutputStream("employee.ser"))) {
            output.writeObject(employee);
            System.out.println("Object serialized successfully.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

The employee.ser file contains the serialized representation of the object.

The .ser extension is only a common naming convention. Java does not require serialized files to use this extension.


Understanding writeObject()

The writeObject() method is used to serialize an object.

Main.java
Run Online
output.writeObject(employee);

The object must be serializable. Otherwise, Java can throw a NotSerializableException.

For example:

Main.java
Run Online
class Employee {
    String name;
}

Trying to serialize an Employee object from this class without implementing Serializable can result in an exception.


Java Deserialization

Deserialization reconstructs an object from a serialized byte stream.

Here is a basic example:

Main.java
Run Online
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class Main {
    public static void main(String[] args) {
        try(ObjectInputStream input = new ObjectInputStream( new FileInputStream("employee.ser"))) {
            Employee employee =(Employee) input.readObject();
            System.out.println("Object deserialized successfully.");
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

The return type of readObject() is Object, so a cast is commonly required:

Main.java
Run Online
Employee employee =(Employee) input.readObject();

Complete Serialization and Deserialization Example

The following example demonstrates both operations:

Main.java
Run Online
import java.io.*;
class Student implements Serializable {
    private String name;
    private int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    } public void display() {
        System.out.println("Name: " + name);
        System.out.println("Age: " + age);
    }
}public class Main {
    public static void main(String[] args) {
        Student student = new Student("Amit", 22);
        // Serialization
        try(ObjectOutputStream output = new ObjectOutputStream( new FileOutputStream("student.ser"))) {
            output.writeObject(student);
            System.out.println("Serialization completed.");
        } catch (IOException e) {
            e.printStackTrace();
        } // Deserialization
        try(ObjectInputStream input = new ObjectInputStream( new FileInputStream("student.ser"))) {
            Student restored =(Student) input.readObject();
            System.out.println("Deserialization completed.");
            restored.display();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Possible output:

Main.java
Run Online
Serialization completed.Deserialization completed.Name: AmitAge: 22

What Happens to Object Fields During Serialization?

By default, serializable instance fields are included in the serialized object state.

For example:

Main.java
Run Online
class Product implements Serializable {
    String name;
    double price;
    int quantity;
}

When a Product object is serialized, these fields are part of its serializable state.

However, not every field should necessarily be stored.

For example, passwords, temporary values, caches, or derived data may need different treatment.

This is where the transient keyword becomes useful.


The transient Keyword in Java Serialization

The transient keyword tells Java's default serialization mechanism that a particular instance field should not be serialized.

Syntax

Main.java
Run Online
transient dataType variableName;

Example:

Main.java
Run Online
class User implements Serializable {
    String username;
    transient String password;
}

When the object is serialized, username is included, but password is not.


Example of transient

Main.java
Run Online
import java.io.*;
class User implements Serializable {
    private String username;
    private transient String password;
    public User(String username, String password) {
        this.username = username;
        this.password = password;
    } public void display() {
        System.out.println("Username: " + username);
        System.out.println("Password: " + password);
    }
}public class Main {
    public static void main(String[] args) throws Exception {
        User user = new User("rahul", "secret123");
        try(ObjectOutputStream output = new ObjectOutputStream( new FileOutputStream("user.ser"))) {
            output.writeObject(user);
        } try(ObjectInputStream input = new ObjectInputStream( new FileInputStream("user.ser"))) {
            User restored =(User) input.readObject();
            restored.display();
        }
    }
}

The restored object will have the original username, but the password field will have its default value.

For a String, that default value is:

So the result is conceptually:

Main.java
Run Online
Username: rahulPassword: null

Why Use transient?

transient can be useful when a field:

  • Should not be persisted.

  • Contains sensitive information.

  • Can be calculated again.

  • Represents temporary application state.

  • Contains data that should not be included in the serialized form.

  • Is not suitable for default serialization.

For example:

Main.java
Run Online
class Account implements Serializable {
    private String username;
    private transient String password;
    private transient String temporaryToken;
}

The normal serialization mechanism will not store those transient fields.

Important security note

Using transient is not a complete security strategy. It prevents a field from being included in Java's default serialized state, but sensitive data should still be handled using appropriate security practices.


What Value Does a Transient Field Have After Deserialization?

A transient field is not restored from the serialized stream.

Instead, it receives its normal Java default value unless your class explicitly restores it.

Common defaults include:

Data TypeDefault Value
int0
long0L
double0.0
booleanfalse
char'\u0000'
Object referencenull

Example:

Main.java
Run Online
class Settings implements Serializable {
    private String username;
    private transient int temporaryCounter;
}

After deserialization:

Main.java
Run Online
username → restoredtemporaryCounter → 0

Static Fields and Serialization

A common misconception is that static fields are serialized as part of each object.

They are not part of an individual object's serializable instance state.

Example:

Main.java
Run Online
class User implements Serializable {
    String name;
    static String applicationName = "MyApp";
}

The name field belongs to each object, while applicationName belongs to the class.

Changing the static field does not represent a change to one particular object's serialized instance state.


What If a Field Contains Another Object?

If a serializable object contains another object as an instance field, that referenced object generally also needs to be serializable when default serialization attempts to serialize the complete object graph.

Example:

Main.java
Run Online
class Address implements Serializable {
    String city;
}class Employee implements Serializable {
    String name;
    Address address;
}

Here, both classes implement Serializable.

If Address does not implement Serializable, serializing an Employee containing an Address instance can result in:

Main.java
Run Online
java.io.NotSerializableException

This is because Java's default serialization follows the reachable object graph.


Using transient for a Non-Serializable Field

Suppose a class contains a field whose type is not serializable:

Main.java
Run Online
class Employee implements Serializable {
    String name;
    transient SomeNonSerializableObject object;
}

Because the field is transient, Java's default serialization does not attempt to serialize that field.

This can be useful when the field can safely be recreated later.


The serialVersionUID

serialVersionUID is a version identifier used during Java serialization.

It helps Java determine whether the class definition used during deserialization is compatible with the class definition that was used during serialization.

Example:

Main.java
Run Online
class Employee implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int id;
}

The declaration is usually written as:

Main.java
Run Online
private static final long serialVersionUID = 1L;

Why Is serialVersionUID Important?

Imagine an application serializes an object today:

Main.java
Run Online
Employee version 1↓Serialized file

Later, the Employee class changes:

When the old serialized data is read, Java needs to determine whether the stored object is compatible with the current class.

serialVersionUID provides an explicit version identifier for that purpose.


Example Without Explicit serialVersionUID

You might write:

Main.java
Run Online
class Employee implements Serializable {
    private String name;
    private int id;
}

Java can calculate a default serial version identifier from aspects of the class definition.

This generated value can change when the class changes.

That can cause an old serialized stream to fail compatibility checks.

A typical error is:

Main.java
Run Online
java.io.InvalidClassException

Example With Explicit serialVersionUID

A better practice for classes intended to remain serializable across versions is often to declare the identifier explicitly:

Main.java
Run Online
class Employee implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int id;
}

If you make a compatible class evolution, you can keep the same value:

Main.java
Run Online
private static final long serialVersionUID = 1L;

For an intentionally incompatible serialization format change, you can change the value:

Main.java
Run Online
private static final long serialVersionUID = 2L;

The exact compatibility decision depends on how the serialized data is being used and how the class has evolved.


How serialVersionUID Helps With Version Compatibility

Consider version 1:

Main.java
Run Online
class User implements Serializable {
    private static final long serialVersionUID = 1L;
    String name;
}

Later, you add a new field:

Main.java
Run Online
class User implements Serializable {
    private static final long serialVersionUID = 1L;
    String name;
    String email;
}

For a compatible class evolution, the existing serialized data can potentially still be read. The newly introduced field may receive its default value when it is absent from the old serialized data.

For example:

Main.java
Run Online
Old serialized object:name = "Amit"New class:name = "Amit"email = null

This illustrates why serialization compatibility needs to be considered when changing serializable classes.


serialVersionUID Is Not a Database Schema Version

It is important not to confuse serialVersionUID with a general application database version.

It specifically participates in Java's serialization compatibility mechanism.

It does not automatically migrate object data.

For example:

Main.java
Run Online
private static final long serialVersionUID = 1L;

does not mean Java will automatically transform old data into a completely different object structure.

It is a compatibility identifier used during deserialization.


Custom Serialization With writeObject() and readObject()

Java also allows a class to customize how its state is serialized.

A class can define methods with these signatures:

Main.java
Run Online
private void writeObject(ObjectOutputStream out) throws IOException

and:

Main.java
Run Online
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException

Example:

Main.java
Run Online
import java.io.*;
class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String username;
    private transient String password;
    public User(String username, String password) {
        this.username = username;
        this.password = password;
    } private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
    } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        password = "restored-default";
    }
}

Here, defaultWriteObject() and defaultReadObject() allow the class to use the normal serialization behavior for its serializable fields while adding custom logic.


Serialization and Inheritance

If a class implements Serializable, its serializable state can include fields from its serializable superclass hierarchy.

However, a non-serializable superclass is handled differently.

Suppose:

Main.java
Run Online
class Person {
    String name;
}class Employee extends Person implements Serializable {
    int employeeId;
}

Employee is serializable, but Person is not.

During deserialization, the no-argument constructor of the first non-serializable superclass is invoked as part of reconstruction.

Fields declared by that non-serializable superclass are not restored from the serialized stream through normal serialization. They must be initialized by that superclass's construction logic.

This is an important inheritance detail when designing serializable classes.


NotSerializableException

If Java attempts to serialize an object that is not serializable, it can throw:

Main.java
Run Online
java.io.NotSerializableException

Example:

Main.java
Run Online
class Employee {
    String name;
}

Then:

Main.java
Run Online
output.writeObject(new Employee());

can fail because Employee does not implement Serializable.

The solution is not always to make every class serializable. Sometimes the better solution is to mark a particular field as transient or use another data-transfer format.


Serialization and ObjectInputStream

Deserialization should be treated carefully, especially when serialized data comes from an untrusted source.

Java object deserialization can invoke class-specific behavior while reconstructing objects. For applications handling untrusted input, blindly calling:

Main.java
Run Online
new ObjectInputStream(input).readObject();

can create serious security risks.

Modern applications often prefer safer data formats and explicit validation for data crossing trust boundaries.


Java Serialization vs JSON

Java's built-in serialization is not the only way to persist or transfer object data.

A common alternative is JSON.

Java Serialization

Main.java
Run Online
Java Object↓Java serialization↓Binary stream

JSON

Main.java
Run Online
Java Object↓JSON serialization↓Text representation

JSON is widely used for APIs because it is language-independent and easy for many systems to process.

Java native serialization is more tightly coupled to Java classes and has important security and compatibility considerations.


When Should You Use Java Serialization?

Java serialization can still be useful in controlled situations such as:

  • Learning Java object persistence.

  • Certain legacy Java systems.

  • Controlled internal object storage.

  • Specific Java-only workflows where native serialization is appropriate.

However, for new applications, you should evaluate alternatives such as JSON, database persistence, or other explicitly designed serialization formats.

The correct choice depends on interoperability, security requirements, compatibility needs, performance, and the surrounding architecture.


Serialization Best Practices

1. Declare serialVersionUID

For long-lived serializable classes, explicitly declaring:

Main.java
Run Online
private static final long serialVersionUID = 1L;

makes the intended serialization version explicit.

2. Avoid Serializing Sensitive Fields

Use:

Main.java
Run Online
private transient String password;

when a field should not be part of the default serialized state.

But remember that transient alone is not a complete security solution.

3. Be Careful With Object Graphs

Serialization can follow referenced objects. A seemingly simple object can therefore serialize a much larger graph than expected.

Keep the serializable state deliberate and understandable.

4. Do Not Trust Arbitrary Serialized Data

Avoid deserializing untrusted Java serialized streams without appropriate security controls.

5. Consider Alternatives

For APIs and cross-language communication, formats such as JSON are often more suitable than Java native serialization.


Common Mistakes in Java Serialization

Mistake 1: Forgetting Serializable

Trying to serialize this object can cause NotSerializableException.

Mistake 2: Expecting transient Data to Come Back

Main.java
Run Online
transient String password;

After normal deserialization, the field is not restored from the stream.

Mistake 3: Treating serialVersionUID as Automatic Migration

It does not migrate old object data or modify the serialized stream.

Mistake 4: Assuming Every Object in the Graph Is Automatically Safe

Referenced objects may also need to be serializable unless they are excluded or handled specially.

Mistake 5: Deserializing Untrusted Data

Native Java deserialization should not be treated as a safe general-purpose parser for arbitrary external input.


Serialization vs Deserialization: Quick Comparison

FeatureSerializationDeserialization
DirectionObject → bytesBytes → object
Main classObjectOutputStreamObjectInputStream
Main methodwriteObject()readObject()
PurposeStore/transfer object stateReconstruct object
Common exceptionIOException, NotSerializableExceptionIOException, ClassNotFoundException, InvalidClassException
Related conceptSerializableserialVersionUID compatibility

transient vs static

These keywords are sometimes confused.

KeywordMain Meaning
transientExcludes an instance field from default serialization
staticMakes a field belong to the class rather than individual objects

Example:

Main.java
Run Online
class Example implements Serializable {
    String name;
    transient String password;
    static String applicationName;
}

name is an ordinary instance field.

password is an instance field excluded from default serialization.

applicationName is a class-level field rather than part of an individual object's serialized instance state.


throw vs transient

These keywords have completely different purposes.

throw is used to explicitly throw an exception.

Main.java
Run Online
transient String password;

transient affects the default serialization of an instance field.

Do not confuse the two simply because both are Java keywords.


Interview Questions on Java Serialization

1. What is serialization in Java?

Serialization converts an object's state into a byte stream that can be stored or transferred.

2. What is deserialization?

Deserialization reconstructs an object from a serialized byte stream.

3. What is Serializable?

Serializable is a marker interface that enables an object's participation in Java's default serialization mechanism.

4. What does transient do?

It prevents an instance field from being included in default Java serialization.

5. What is serialVersionUID?

It is a version identifier used during serialization compatibility checks.

6. What happens to a transient field after deserialization?

It is not restored from the stream and normally receives its Java default value unless custom restoration logic is used.

7. What exception can occur if an object is not serializable?

NotSerializableException.

8. Why can InvalidClassException occur?

One reason is a mismatch between the serialVersionUID of the serialized class and the current class.

9. Can static fields be serialized as individual object state?

No. Static fields belong to the class rather than to an individual serialized object.

10. Is Java serialization always the best choice for APIs?

No. For many modern APIs, formats such as JSON are preferable because they support language interoperability and explicit data formats.


Practice Example

Try creating a BankAccount class:

Main.java
Run Online
import java.io.Serializable;
class BankAccount implements Serializable {
    private static final long serialVersionUID = 1L;
    private String accountNumber;
    private double balance;
    private transient String pin;
    public BankAccount( String accountNumber, double balance, String pin) {
        this.accountNumber = accountNumber;
        this.balance = balance;
        this.pin = pin;
    } public void display() {
        System.out.println("Account: " + accountNumber);
        System.out.println("Balance: " + balance);
        System.out.println("PIN: " + pin);
    }
}

Serialize and deserialize the object, then observe what happens to the pin field.

You should find that the normal serialized state contains the account information, while the transient PIN is not restored from the serialized stream.


Java Serialization Cheat Sheet

Main.java
Run Online
Serializable↓Marks class as serializableObjectOutputStream↓writeObject()↓SerializationObjectInputStream↓readObject()↓Deserializationtransient↓Exclude instance field from default serializationserialVersionUID↓Serialization compatibility identifier

Important methods

Main.java
Run Online
writeObject(object);
readObject();
defaultWriteObject();
defaultReadObject();

Important exceptions

Main.java
Run Online
IOExceptionNotSerializableExceptionClassNotFoundExceptionInvalidClassException

Suggested Graphics and Page Design

Graphic 1 — Serialization Flow

Create a simple horizontal diagram:

Main.java
Run Online
Java Object↓Serializable↓ObjectOutputStream↓Byte Stream↓File / Storage

Graphic 2 — Deserialization Flow

Main.java
Run Online
Serialized File↓ObjectInputStream↓readObject()↓Java Object

Graphic 3 — transient Concept

Show:

Architecture Diagram
User Object
 ├── username → Serialized
 └── password → transient → Not Serialized

Graphic 4 — serialVersionUID

Show two class versions:

Main.java
Run Online
Employee v1serialVersionUID = 1L↓Serialized Data↓Employee v2serialVersionUID = 1L↓Compatibility Check

Use a clean Java-themed diagram style with simple boxes, arrows, and short labels rather than decorative stock imagery.


Runnable Code Example: Java Serialization

Java 17+
Main.java
Run Online
import java.io.*;
class UserSession implements Serializable {
    private static final long serialVersionUID = 1L;
    private String username;
    private transient String secretPin;
    // Will not be serialized
    public UserSession(String username, String secretPin) {
        this.username = username;
        this.secretPin = secretPin;
    } @Override public String toString() {
        return "User: " + username + ", PIN: " + secretPin;
    }
}public class SerializationDemo {
    public static void main(String[] args) throws Exception {
        UserSession session = new UserSession("alex_dev", "SECRET_99");
        System.out.println("Before Serialization: " + session);
    }
}

Common Java Technical Interview Questions & Answers

Interview Prep
Q1

What does the transient modifier keyword do in Java?

It marks a field so that its value is excluded and skipped during object serialization.
Q2

What happens if serialVersionUID is missing or mismatched during deserialization?

The JVM throws an InvalidClassException due to class structural incompatibility.

Key Takeaways, Best Practices & Clean Code Rules

Java Best Practice & Production Rules

Explicitly declare a private static final long serialVersionUID = 1L in all Serializable classes to prevent deserialization failures.