The cart that vanished
Riya's canteen app lets students build a cart before paying. If the app closes, the cart disappears and students have to add everything again. She could write each item as text, but the cart has items, quantities, a coupon and the student's details, all objects pointing to objects.
Sir, can't I just save the whole cart object as it is?
You can. Mark the class Serializable and an ObjectOutputStream will flatten the cart and everything it points to into bytes.
And the student's UPI PIN that's stored in there?
That must never reach a file. Mark it transient. Let's see exactly what gets written.
What are serialization and deserialization?
Serialization is the process of converting an object's state, together with the objects it references (its object graph), into a sequence of bytes that can be stored or transmitted. Deserialization reconstructs new objects with the same state from those bytes. In Java, a class opts in by implementing the marker interface java.io.Serializable; objects are written with ObjectOutputStream.writeObject() and read with ObjectInputStream.readObject(). Fields declared transient and static fields are not serialized. A serialVersionUID identifies the version of the class and is checked during deserialization; a mismatch causes InvalidClassException.
Serialization packs an object flat into a box of bytes. Deserialization unpacks the box and builds an identical object, a twin, not the original.
Words you'll meet in this lesson
long that labels the class version stored in the bytes.AC ED 00 05.What gets saved?
| Field | Saved? | After reading |
|---|---|---|
Normal instance field (int points) | Yes | Saved value |
| Reference to a Serializable object | Yes, the whole object | A new equal object |
| Reference to a non-Serializable object | — | NotSerializableException while writing |
transient field | No | Default: null, 0, false |
static field | No | Whatever the class has now |
Exam tip: “Serializable is a marker interface. transient and static fields are not serialized. Deserialization does not call the constructor of a serializable class. Declare private static final long serialVersionUID to control version compatibility.”
The rules in one class
class Customer implements Serializable { // 1. opt in (marker interface)
private static final long serialVersionUID = 1L; // 2. version number of the format
String name; // saved
int points; // saved
Address home; // 3. must be Serializable too
transient String otp; // 4. skipped, comes back null
static int count; // 5. class data, never saved
}Default serialization
Implement Serializable; Java writes all non-transient fields. Enough for most cases.
writeObjecttransient
Leave out secrets (PINs, passwords), caches and things that can't be serialized (open files, threads).
skip a fieldCustom writeObject/readObject
Private methods in your class to encrypt, validate or convert fields yourself.
advancedJSON instead
For APIs and long-term storage, text formats (JSON with Jackson/Gson) are readable, language-neutral and safer.
common todayWhen the class changes
If you serialize with one version of a class and read with another whose serialVersionUID differs (or, if you didn't declare one, whose computed UID changed because you edited the class), reading fails:
java.io.InvalidClassException: Customer; local class incompatible:
stream classdesc serialVersionUID = 1, local class serialVersionUID = 2Never deserialize data you don't trust. readObject can create objects of any serializable class on your classpath, without running constructors. Attackers have used crafted byte streams to run code on servers. Accept JSON (validated) from outside, or use deserialization filters (ObjectInputFilter) to allow only expected classes.
What writeObject and readObject do
Open an object stream
It wraps a byte stream and immediately writes the headerObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cart.ser"));AC ED 00 05.writeObject(cart)
It checks the class implements Serializable, writes a class description (name, serialVersionUID, field names and types), then the values of each non-transient, non-static field.Follow references
For each reference field it serializes the referenced object the same way, recursively. Objects already written in this stream are stored as a short back-reference, so cycles and shared objects work.readObject()
It reads the class description, checks the UID, allocates a new object without calling its constructor, and sets the fields from the stream. Transient fields keep default values.Cart cart = (Cart) in.readObject();Cast and use
readObjectreturnsObject; you cast it. It throwsClassNotFoundExceptionif the class isn't available, andInvalidClassExceptionif the versions don't match.
Object to bytes and back: both views
Left: a Customer saved to a file and read back later. Right: how serialization walks an object graph and keeps a shared object shared.
- A
Customerobject lives on the heap with a name, loyalty points and a one-time password. Objects disappear when the program ends. ObjectOutputStream.writeObject(c)serializes it: turns the object's state into a sequence of bytes.- The bytes go into customer.ser. They start with the magic number AC ED, then the class name and field values. The OTP field is
transient, so it's skipped. - The program can now end. Only the file remains.
- Later (even in another program),
ObjectInputStream.readObject()deserializes the bytes into a brand-new Customer. No constructor of Customer runs. - The new object has the saved name and points; the transient OTP comes back as its default value,
null. It is a different object from the original:back == cwould be false.
- Objects usually point to other objects. Here both
homeandworkrefer to the same Address #2. writeObjectwrites the Customer's class description and its non-transient, non-static fields.- A reference field means the referenced object must be written too, so it follows
hometo Address #2. Every object reached this way must beSerializable. workpoints to an object already written, so only a short back-reference (a handle) is stored. Each object is written once.readObjectrecreates the whole graph as new objects: Customer #3 and Address #4.- Because of the handle, both fields of #3 point to the same #4, just as before. Static fields belong to the class, not the object, so they are never part of the bytes.
Where serialization is used
Java serialization still appears in caches, sessions and older systems, and the same idea, turning objects into bytes, is everywhere in modern formats such as JSON and Protocol Buffers.
Watch it with an example: a shopping cart that survives an app restart
A shopping app saves the customer's cart when the app is closed and restores it when it opens again.
- While shopping, the cart is an object with a list of items.
- When the app closes, it writes the cart with
writeObjectto cart.ser in the app's private storage. (Illustrative size.) - The app process ends; only the file remains.
- Next launch,
readObjectrebuilds the cart: same items, same prices as when it was saved. - Saved prices may be old, so the app re-checks prices and coupons with the server. Many real apps save such state as JSON instead of Java serialization, for reasons you'll see in Part 3.
1. Web sessions across servers
Some servers serialize HTTP session objects (logged-in user, cart) to share them across machines or survive restarts.
2. Caches
Distributed caches store objects as bytes; Java serialization or a faster format turns them into bytes and back.
3. Saving app state
Games, desktop tools and mobile apps save progress and drafts so users can continue later.
4. APIs (with JSON)
REST APIs serialize Java objects to JSON text and deserialize requests back into objects: the same idea in a portable, safer format.
Simplified. New systems usually prefer JSON or Protocol Buffers over Java's built-in serialization, mainly for safety and compatibility with other languages.
Save a customer, load a twin
import java.io.*;
class Customer implements Serializable {
private static final long serialVersionUID = 1L;
String name;
int points;
transient String otp;
Customer(String name, int points, String otp) {
this.name = name;
this.points = points;
this.otp = otp;
}
}
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Customer c = new Customer("Asha", 120, "4821");
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("customer.ser"))) {
out.writeObject(c);
}
System.out.println("saved " + new File("customer.ser").length() + " bytes");
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("customer.ser"))) {
Customer back = (Customer) in.readObject();
System.out.println(back.name + ", " + back.points + " points, otp=" + back.otp);
System.out.println("same object? " + (back == c));
}
}
}implements SerializableWithout it,writeObjectthrowsNotSerializableException.transient String otpNot written; comes back asnull.(Customer) in.readObject()Returns Object, so cast. ThrowsClassNotFoundException(checked) if the class is missing.back == cfalse: deserialization always creates new objects.
A Customer saved and restored
Recorded from the real JVM. Watch a second Customer object appear on the heap after readObject: same name and points, OTP null, different object number.
Worked examples: real problems, solved and traced in memory
Three cases you'll meet: saving game progress, protecting a secret with transient, and saving a whole list of objects in one call. Watch new objects appear on the heap when they are read back.
Save and load game progress
Problem statement
A quiz game must save the player's progress (name, level, score, list of badges) when they quit and restore it next time. Make the class serializable, save it, load it, and show the restored progress.
Ravi, level 7, score 4350, badges: Speed, Streak
Ravi is on level 7 with 4350 points badges: [Speed, Streak]
Approach
Progress implements Serializable, with aserialVersionUID.- Fields include an
ArrayList<String>, which is itself serializable. - Write with ObjectOutputStream, read with ObjectInputStream, cast the result.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Never save the PIN: transient
Problem statement
A banking app caches a Session object to disk so a user doesn't have to re-select their account. The session holds the account number, the last screen and the user's PIN. The PIN must never be written to disk.
account XX4521, screen statements, PIN 7391
file mentions account? true file mentions PIN? false restored: XX4521, statements, pin=null
Approach
- Mark
pinastransient. - Serialize, then look inside the raw bytes for the PIN text to prove it's absent.
- Deserialize and show the PIN came back as null.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Save a whole product list in one call
Problem statement
A shop caches its product catalogue (a list of Product objects) to a file so it can start quickly without waiting for the server. Save the list with one writeObject call, reload it, and print the cheapest product.
Rice 5kg Rs 450, Oil 1L Rs 165, Sugar 1kg Rs 48
3 products loaded; cheapest: Sugar 1kg Rs 48
Approach
Product implements Serializable.ArrayListis serializable, so writing the list writes every product in it.- Read it back and cast to
List<Product>(an unchecked cast, so add@SuppressWarnings).
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Check yourself
Quick revision
Lesson 13.4 in one screen
- Serialization: object graph → bytes. Deserialization: bytes → new objects.
- Opt in with the marker interface
Serializable; every object in the graph must be serializable. ObjectOutputStream.writeObject,ObjectInputStream.readObject(returns Object; cast it).transientandstaticfields are not saved; transient comes back as the default value.- Constructors of serializable classes don't run on deserialization.
- Declare
serialVersionUID; a mismatch givesInvalidClassException. - Never deserialize untrusted data; prefer JSON for external data.
Interview and viva questions
What is serialization, and why is Serializable a marker interface?
Serialization converts an object's state into bytes for storage or transfer. Serializable has no methods: it only marks a class as permitted, so classes don't get serialized (and their internals exposed) by accident.
What is the use of the transient keyword?
It excludes a field from serialization. Use it for secrets (passwords, PINs), derived or cached values that can be recomputed, and fields whose types aren't serializable (threads, streams, connections). After deserialization the field has its default value.
What is serialVersionUID and what happens if you don't declare it?
It's a version number stored in the stream and checked when reading. If you don't declare one, Java computes it from the class's structure, so small changes (adding a method or field) change it and old files can no longer be read (InvalidClassException). Declaring it explicitly keeps compatible changes working.
Is the constructor called during deserialization?
Not for the serializable class or its serializable superclasses. The JVM calls only the no-arg constructor of the first non-serializable superclass (often Object), then restores fields from the stream. Validation in constructors is therefore bypassed.
What are the risks of Java serialization?
Deserializing untrusted data can instantiate unexpected classes and trigger “gadget chains” leading to remote code execution. It's also Java-only, fragile across class changes, and exposes private fields in the bytes. Mitigate with ObjectInputFilter allow-lists, or use JSON/Protocol Buffers with validation.
Practice problems
Serialize objects, control what's saved with transient, and handle the exceptions. Each program writes and reads its own file. The last three are business tasks.
Save and load one object
Make a serializable Book (title, price). Save one and load it back.
Java Basics, 399
Java Basics Rs 399
Show solution and explanation
import java.io.*;
class Book implements Serializable {
private static final long serialVersionUID = 1L;
String title;
int price;
}
public class Main {
public static void main(String[] args) throws Exception {
Book b = new Book();
b.title = "Java Basics";
b.price = 399;
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.ser"))) {
out.writeObject(b);
}
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("book.ser"))) {
Book copy = (Book) in.readObject();
System.out.println(copy.title + " Rs " + copy.price);
}
}
}How it works: Serializable is a marker interface: no methods, it just gives permission. readObject returns Object, so cast it.
transient fields come back empty
Serialize a Login with a username and a transient password and print both after loading.
asha / secret123
user=asha, password=null
Show solution and explanation
import java.io.*;
class Login implements Serializable {
private static final long serialVersionUID = 1L;
String user = "asha";
transient String password = "secret123";
}
public class Main {
public static void main(String[] args) throws Exception {
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("login.ser"))) {
out.writeObject(new Login());
}
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("login.ser"))) {
Login l = (Login) in.readObject();
System.out.println("user=" + l.user + ", password=" + l.password);
}
}
}How it works: transient fields get default values after deserialization (null, 0, false). Initializers like = "secret123" don't run for a deserialized object.
The magic number
Serialize a String and print the first four bytes of the file in hex.
"hello"
AC ED 00 05 (12 bytes)
Show solution and explanation
import java.io.*;
import java.nio.file.*;
public class Main {
public static void main(String[] args) throws IOException {
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("s.ser"))) {
out.writeObject("hello");
}
byte[] b = Files.readAllBytes(Path.of("s.ser"));
System.out.printf("%02X %02X %02X %02X (%d bytes)%n", b[0], b[1], b[2], b[3], b.length);
}
}How it works: every Java serialization stream starts with the magic number AC ED and version 00 05. Security tools look for these bytes in network traffic.
Constructors don't run
Show that deserialization does not call the class's constructor: count constructor calls in a static counter.
(none)
seat 14, constructor calls: 1
Show solution and explanation
import java.io.*;
class Ticket implements Serializable {
private static final long serialVersionUID = 1L;
static int made = 0;
int seat;
Ticket(int seat) { this.seat = seat; made++; }
}
public class Main {
public static void main(String[] args) throws Exception {
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("t.ser"))) {
out.writeObject(new Ticket(14));
}
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("t.ser"))) {
Ticket t = (Ticket) in.readObject();
System.out.println("seat " + t.seat + ", constructor calls: " + Ticket.made);
}
}
}How it works: the JVM creates the object without running Ticket's constructor and then fills in the fields from the stream. So any validation in your constructor is skipped; that's one reason deserializing untrusted data is risky.
Static fields aren't saved
Serialize an object, change a static field, deserialize, and show the static value is the current one, not the saved one.
(none)
user=meena, theme=dark
Show solution and explanation
import java.io.*;
class Config implements Serializable {
private static final long serialVersionUID = 1L;
static String theme = "light";
String user = "meena";
}
public class Main {
public static void main(String[] args) throws Exception {
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cfg.ser"))) {
out.writeObject(new Config());
}
Config.theme = "dark";
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("cfg.ser"))) {
Config c = (Config) in.readObject();
System.out.println("user=" + c.user + ", theme=" + Config.theme);
}
}
}How it works: serialization saves an object's state; static fields belong to the class, so they're not part of it.
Catch NotSerializableException
A serializable Order has a field of a non-serializable type Printer. Catch the exception and print which class caused it.
(none)
cannot save: Printer is not Serializable
Show solution and explanation
import java.io.*;
class Printer {
String model = "TP-80";
}
class Order implements Serializable {
private static final long serialVersionUID = 1L;
int id = 501;
Printer printer = new Printer();
}
public class Main {
public static void main(String[] args) throws IOException {
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("o.ser"))) {
out.writeObject(new Order());
} catch (NotSerializableException e) {
System.out.println("cannot save: " + e.getMessage() + " is not Serializable");
}
}
}How it works: the whole object graph must be serializable. Fix it by making Printer serializable, or by marking the field transient and recreating it after loading.
Persist a customer list
Save three customers (name, city, loyalty points) as one list, reload it, and print the total points.
3 customers
3 customers, 495 points in total
Show solution and explanation
import java.io.*;
import java.util.*;
class Customer implements Serializable {
private static final long serialVersionUID = 1L;
String name, city;
int points;
Customer(String n, String c, int p) { name = n; city = c; points = p; }
}
public class Main {
@SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
List<Customer> list = new ArrayList<>(List.of(
new Customer("Asha", "Pune", 120), new Customer("Ravi", "Nagpur", 300), new Customer("Imran", "Pune", 75)));
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("customers.ser"))) {
out.writeObject(list);
}
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("customers.ser"))) {
List<Customer> loaded = (List<Customer>) in.readObject();
int total = 0;
for (Customer c : loaded) total += c.points;
System.out.println(loaded.size() + " customers, " + total + " points in total");
}
}
}How it works: collections serialize their elements, so one call saves everything. Remember the cast from Object is unchecked: the file could contain anything.
Shared address saved once
Two employees share one office Address object. Serialize both in one stream and show they still share the same Address after loading.
2 employees, 1 office
same office object? true, city Mumbai
Show solution and explanation
import java.io.*;
class Address implements Serializable {
private static final long serialVersionUID = 1L;
String city = "Mumbai";
}
class Employee implements Serializable {
private static final long serialVersionUID = 1L;
String name;
Address office;
Employee(String n, Address a) { name = n; office = a; }
}
public class Main {
public static void main(String[] args) throws Exception {
Address hq = new Address();
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("staff.ser"))) {
out.writeObject(new Employee("Asha", hq));
out.writeObject(new Employee("Ravi", hq));
}
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("staff.ser"))) {
Employee a = (Employee) in.readObject();
Employee r = (Employee) in.readObject();
System.out.println("same office object? " + (a.office == r.office) + ", city " + r.office.city);
}
}
}How it works: within one stream, an object that was already written is stored as a back-reference, so sharing is preserved. Objects must be read in the same order they were written.
Version check with serialVersionUID
Print the serialVersionUID that Java uses for a class, so a team can fix it explicitly before shipping.
class Invoice
Invoice serialVersionUID = 202610 fields saved: 2
Show solution and explanation
import java.io.*;
class Invoice implements Serializable {
private static final long serialVersionUID = 2026_10L;
String number;
double amount;
}
public class Main {
public static void main(String[] args) {
ObjectStreamClass desc = ObjectStreamClass.lookup(Invoice.class);
System.out.println(desc.getName() + " serialVersionUID = " + desc.getSerialVersionUID());
System.out.println("fields saved: " + desc.getFields().length);
}
}How it works: the UID is stored in every serialized stream. When reading, Java compares it with the current class; if they differ you get InvalidClassException. Declaring it yourself keeps compatible changes (like adding a field) from breaking old files.
Real-world scenario questions (asked in company interviews)
Serialization questions test both mechanics and judgement: when to use it, and when not to.
After the update, saved carts won't load
A desktop billing app saves open bills with Java serialization. After a release that added one field to Bill, every saved bill fails with InvalidClassException: local class incompatible. What happened, and how do you prevent it?
The interviewer is checking: serialVersionUID.
Show a strong answer
Billhad no declared serialVersionUID, so Java computed one; adding a field changed it and old streams no longer match.- Declare
private static final long serialVersionUIDfrom the first release. Adding a field is then a compatible change: old data loads with the new field at its default. - For this release, re-add the old computed UID (find it with the
serialvertool) to recover existing files.
Avoid: telling users to delete their saved bills.
Card numbers found in session files
A security audit finds full card numbers inside the server's serialized session files on disk. The session object holds a PaymentInfo field. What would you change?
The interviewer is checking: transient and handling sensitive data.
Show a strong answer
- Serialization writes every non-transient field in plain bytes; there's no encryption.
- Don't keep card data in the session at all: store a token from the payment gateway. If a field must exist, mark it
transient. - Encrypt session storage and restrict file permissions.
Avoid: making the field private and assuming that hides it; private fields are serialized too.
An endpoint that accepts serialized objects
An internal API receives orders as Java-serialized objects in the HTTP body and calls readObject(). The team wants to expose it to partners. What's your advice?
The interviewer is checking: deserialization vulnerabilities.
Show a strong answer
- Deserializing attacker-controlled bytes can instantiate arbitrary classes from the classpath and lead to remote code execution.
- Switch the API to JSON with a strict schema and validation; it's also language-neutral for partners.
- If Java serialization must stay, apply an
ObjectInputFilterallow-list of expected classes, size limits, and authentication.
Avoid: “we only accept our Order class”. The cast happens after readObject has already built the objects.
Serialized files are huge and slow
A Customer object is cached by serializing it, but each file is several MB. Investigation shows Customer has a reference to a shared CatalogService that holds the entire product catalogue. Explain and fix.
The interviewer is checking: object graphs and transient.
Show a strong answer
- Serialization follows every reference, so the whole catalogue is written with each customer.
- Mark service references
transientand re-inject them after loading, or serialize a small DTO with only the customer's data.
Avoid: compressing the files and ignoring the design problem.