Senior 4 min · March 05, 2026

Java serialization: Missing serialVersionUID Crashed Payments

Missing explicit serialVersionUID caused InvalidClassException, crashing payment processing.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Serialization converts Java object graphs into portable byte streams for storage or network transfer
  • ObjectOutputStream writes class metadata, object data, and graph references in a standard binary format
  • serialVersionUID ensures class version compatibility; mismatch throws InvalidClassException
  • Externalizable gives full control over serialization format and can be faster than default Serializable
  • Deserialization is a security risk; always validate input or use alternative formats like JSON
  • Performance: Java serialization is ~3-5x slower than custom Externalizable or Protocol Buffers
Plain-English First

Imagine you've built an intricate LEGO castle and want to mail it to a friend, but it's too big. You photograph each brick's position, pack the instructions into an envelope, and ship them. Your friend rebuilds the castle from those instructions. Serialization does that for Java objects — freezes a live object into bytes you can store or send. Deserialization rebuilds it. But if your LEGO set's instructions change version (say, a new piece added), the reconstruction fails unless you planned for it.

Every distributed Java system — from REST APIs that cache session state to Spark jobs shuffling terabytes of data — needs to freeze an object's state and revive it elsewhere. Serialization is the mechanism baked into the JDK since Java 1.1. Yet it's one of the most misunderstood APIs. Log4Shell and countless gadget-chain exploits exist because developers trusted it without knowing what actually happens under the hood.

The problem serialization solves is simple: objects live in heap memory, which is process-local and ephemeral. The moment your JVM shuts down, that memory is gone. Serialization provides a contract to convert an object graph — not just a single object, but every object it references, recursively — into a portable, linear byte stream that can cross process boundaries, machines, and time.

By the end you'll understand the exact binary format ObjectOutputStream writes, why serialVersionUID is both your best friend and worst enemy, when to use Externalizable, how performance compares to alternatives, and the security traps you must avoid before shipping serialization code to production.

What is Serialization in Java?

Serialization converts objects into byte streams. You probably know that. But think about the why first: without serialization, you can't send objects over a network, cache them in Redis, or store them in a file. Every time your app talks to another service or survives a restart, serialization is involved.

The core workflow uses ObjectOutputStream for writing and ObjectInputStream for reading. Java handles cycles, shared references, and inheritance automatically. But that convenience comes at a cost: performance overhead, security risks, and tight coupling between class structure and the serialized format. That coupling is what bites you at 3 AM.

Here's the simplest example — a Person class that can be written and read back:

io/thecodeforge/serialization/Person.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package io.thecodeforge.serialization;

import java.io.*;

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    private transient String password;

    public Person(String name, int age, String password) {
        this.name = name;
        this.age = age;
        this.password = password;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }

    public static void main(String[] args) throws Exception {
        Person p = new Person("Alice", 30, "secret");

        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("person.ser"))) {
            oos.writeObject(p);
        }

        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("person.ser"))) {
            Person restored = (Person) ois.readObject();
            System.out.println(restored);  // password is null
        }
    }
}
Real-world note:
The transient keyword prevents password from being serialized. If you forget to mark a non-serializable field, you get NotSerializableException. Always audit your object graph before shipping.
Production Insight
Serialization is everywhere: session replication in clusters, RMI calls, distributed caches, and Spark shuffle operations.
A common production trap: serializing a large object graph (say, a User with 10,000 Orders) can cause a 50 MB heap spike just for the stream's internal cache. Monitor memory before and after serialization calls.
Rule: always wrap ObjectOutputStream in try-with-resources to ensure the stream is closed and flushed — otherwise corrupted files and resource leaks follow.
Key Takeaway
Serialization converts object graphs to portable byte streams.
Java's default mechanism is convenient but couples class structure to the format.
Never deploy a Serializable class without explicit versioning and testing.

How ObjectOutputStream Writes Objects — Binary Format Internals

When you call writeObject(), the JVM traverses the object graph depth-first. For each unique object, it writes: - A class descriptor: the fully qualified class name, serialVersionUID, and metadata about fields (type, name, whether it's Serializable). - Object data: field values in declaration order, using writeObject for nested objects. - Back references: if the same object appears twice, the second occurrence is replaced by a handle pointing to the first.

The stream format uses a binary protocol with magic bytes (0xAC 0xED 0x00 0x05), followed by a version stamp and class descriptor records. Understanding this format helps when debugging corruption or version issues.

Let's look at a practical example that writes two objects sharing a reference:

io/thecodeforge/serialization/GraphSerialization.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package io.thecodeforge.serialization;

import java.io.*;

public class GraphSerialization {
    public static void main(String[] args) throws Exception {
        Address addr = new Address("123 Main St");
        Person p1 = new Person("Alice", 30, "secret", addr);
        Person p2 = new Person("Bob", 25, "pass", addr);  // same address

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
            oos.writeObject(p1);
            oos.writeObject(p2);
        }

        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        try (ObjectInputStream ois = new ObjectInputStream(bis)) {
            Person r1 = (Person) ois.readObject();
            Person r2 = (Person) ois.readObject();
            // r1.getAddress() == r2.getAddress() — same instance
            System.out.println(r1.getAddress() == r2.getAddress());
        }
    }
}

class Address implements Serializable {
    private static final long serialVersionUID = 1L;
    private String street;
    public Address(String street) { this.street = street; }
}
Stream format mental model
  • Magic bytes (AC ED 00 05) identify the stream as Java serialization.
  • A class descriptor includes class name, UID, number of fields, and field descriptors.
  • Object data appears as a sequence of field values; strings are written with a length prefix.
  • Back references use a handle index starting from 0x7E0000 to avoid duplication.
Production Insight
The depth-first traversal means the entire object graph is serialized in one write. If you have a circular reference (e.g., parent-child back-reference), Java handles it via backref handles. But large graphs can cause memory pressure during serialization because ObjectOutputStream caches all written objects.
In production, serializing a 100k-object graph might consume tens of MB of heap just for the stream cache. Monitor memory during serialization calls.
Pro tip: use a custom ObjectOutputStream that resets the stream periodically to release cached references.
Key Takeaway
ObjectOutputStream writes class descriptors recursively.
Back references avoid duplicate data for the same object.
Large graphs inflate memory — measure before you ship.

serialVersionUID: The Silent Contract Breaker

Every Serializable class has a version number called serialVersionUID. If you don't declare it explicitly, the JVM computes one from class structure — fields, methods, superclass chain. The hash changes when you add/remove fields, change types, or modify modifiers. This computed UID is fragile — a simple field rename breaks compatibility.

The fix: always declare an explicit serialVersionUID. Once set, you control versioning. You can change the class as long as you can read old streams. Common strategies: - Initial version: serialVersionUID = 1L - Backward compatible change (add field with default value): keep same UID, provide default via readObject - Breaking change: increment UID, handle old streams via readResolve or custom readObject

Here's an example of handling a new field in a backward-compatible way:

io/thecodeforge/serialization/Employee.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package io.thecodeforge.serialization;

import java.io.*;

public class Employee implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;
    private String department;
    private String email;  // added in v2

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ObjectInputStream.GetField fields = ois.readFields();
        name = (String) fields.get("name", null);
        department = (String) fields.get("department", null);
        // If email wasn't in the stream, use default
        email = (String) fields.get("email", "default@company.com");
    }

    private void writeObject(ObjectOutputStream oos) throws IOException {
        ObjectOutputStream.PutField fields = oos.putFields();
        fields.put("name", name);
        fields.put("department", department);
        fields.put("email", email);
        oos.writeFields();
    }
}
Common Trap: Missing serialVersionUID
When you're running in a cluster and two JVMs have different class versions, deserialization fails silently. Always declare serialVersionUID and treat every class change as a schema evolution event.
Production Insight
A real incident: a team added a new field to a Serializable class and forgot to update the UID. The JVM computed the same UID because it uses only certain structural features — but the old streams had no value for the new field. Deserialization succeeded, but the field was null, causing NPEs downstream.
The fix: always implement readObject to handle default values for new fields. Use ObjectStreamField API to check what fields exist in the stream.
Lesson: don't assume backward compatibility — test deserialization of old data in CI after every release.
Key Takeaway
Explicit serialVersionUID eliminates JVM version guessing.
Add fields backward-compatibly with default values in readObject.
Test deserialization of old data in your CI pipeline.

Externalizable vs Serializable: Performance and Control

Serializable is the default interface. It uses reflection to write all non-transient, non-static fields. Reflection is slow and serializes all fields regardless of whether they matter.

Externalizable gives you full control. You implement writeExternal and readExternal, writing only the fields you need. This can be 3-5x faster and produces smaller streams. Use it when: - Performance is critical (high-throughput messaging) - You need to serialize only a subset of fields - The class structure is complex with derived state

io/thecodeforge/serialization/CompactPoint.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package io.thecodeforge.serialization;

import java.io.*;

public class CompactPoint implements Externalizable {
    private int x, y;
    private transient double magnitude;  // derived, not serialized

    // Mandatory public no-arg constructor
    public CompactPoint() {}

    public CompactPoint(int x, int y) {
        this.x = x;
        this.y = y;
        this.magnitude = Math.sqrt(x*x + y*y);
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeInt(x);
        out.writeInt(y);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        x = in.readInt();
        y = in.readInt();
        this.magnitude = Math.sqrt(x*x + y*y);  // recompute
    }

    @Override
    public String toString() {
        return "CompactPoint(" + x + "," + y + ", mag=" + magnitude + ")";
    }
}
Serializable vs Externalizable
  • Serializable uses reflection and writes all fields; Externalizable requires manual field management.
  • Externalizable can skip null fields, derived state, or compress data bytes for smaller payloads.
  • Externalizable needs a public no-arg constructor; Serializable doesn't.
  • Serializable supports versioning via readObject; Externalizable requires manual version tracking.
Production Insight
In a high-throughput trading system, switching from Serializable to Externalizable reduced serialization time by 60% and decreased network payload size by 40%. The trade-off: more code to maintain and explicit version handling.
But don't optimise prematurely. Profile first — if serialization is not the bottleneck, stick with Serializable for simplicity.
One more thing: Externalizable doesn't handle class metadata automatically — if you rename a field, old streams break unless you implement versioning logic yourself.
Key Takeaway
Externalizable gives speed and control at the cost of code.
Use Serializable by default; switch to Externalizable when profiling indicates a hotspot.
Always test both approaches under realistic load.

Security: Deserialization Attacks and Prevention

Deserialization of untrusted data is one of the biggest security risks in Java. Attackers craft byte streams that, when deserialized, instantiate classes that execute arbitrary code — these are called gadget chains. Frameworks like Spring, Apache Commons Collections, and even the JDK have known gadgets.

Prevention strategies
  • Validate input: never deserialize data from untrusted sources. Use a whitelist of allowed classes.
  • Deserialization filter: use JVM-wide filter with ObjectInputFilter (since Java 9).
  • Use alternatives: JSON/Protobuf for untrusted data. Serialization is for trusted internal communication.
  • Isolate deserialization: run in a restricted security manager or separate JVM.

Here's a custom ObjectInputStream that enforces a class whitelist:

io/thecodeforge/security/SafeDeserialization.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package io.thecodeforge.security;

import java.io.*;
import java.util.function.Predicate;

public class SafeDeserialization {

    public static Object deserializeSafely(byte[] data, Predicate<Class<?>> classFilter)
            throws IOException, ClassNotFoundException {
        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data)) {
            @Override
            protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
                Class<?> clazz = super.resolveClass(desc);
                if (!classFilter.test(clazz)) {
                    throw new SecurityException("Blocked: " + clazz.getName());
                }
                return clazz;
            }
        }) {
            return ois.readObject();
        }
    }

    public static void main(String[] args) throws Exception {
        byte[] serialized = /* ... */;
        Predicate<Class<?>> filter = clazz -> clazz.getCanonicalName().startsWith("io.thecodeforge.");
        Object obj = deserializeSafely(serialized, filter);
    }
}
Production Security: Never Deserialize Untrusted Data
Log4Shell exploited deserialization. If you deserialize data from HTTP requests, message queues, or user-uploaded files, you're vulnerable. Always use a class whitelist and prefer JSON/Protobuf for external input.
Production Insight
A fintech company was breached because their message queue deserialized incoming payloads from a partner. The attacker sent a crafted payload that used Commons Collections gadget chain to spawn a reverse shell. The fix: switch to JSON for inter-service communication and add an ObjectInputFilter on all deserialization boundaries.
And don't think you're safe just because you only deserialize your own classes — if your classpath includes any library with known gadgets (Commons Collections, Spring, even Java's built-in Swing libraries), you're at risk. Keep dependencies updated and use a whitelist that explicitly denies known gadget classes.
Key Takeaway
Never deserialize untrusted data.
Use whitelist filters or alternative formats for external input.
Know your dependencies — common libraries may contain dangerous gadgets.

Performance Considerations and Alternatives

Java's default serialization is convenient but not fast. It uses reflection, writes class metadata repeatedly, and has no compression. Here are realistic throughput numbers from production benchmarks: - Java Serialization: ~50-100 MB/s - Externalizable (manual): ~200-300 MB/s - JSON (Jackson): ~150-250 MB/s - Protocol Buffers: ~400-600 MB/s - Kryo (custom Java serializer): ~300-500 MB/s

In addition to throughput, consider size. Java serialization includes class names and field descriptors, so a simple object might become 200+ bytes. Protocol Buffers and MessagePack produce much smaller payloads.

When to use alternatives
  • High throughput / low latency: Protocol Buffers, FlatBuffers
  • Interoperability: JSON, Avro
  • Java-only with performance: Kryo, FST
  • Human-readable: JSON

Here's a JMH benchmark that compares Java serialization vs Kryo for the same object:

io/thecodeforge/benchmark/SerializationBenchmark.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package io.thecodeforge.benchmark;

import org.openjdk.jmh.annotations.*;
import java.io.*;
import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Thread)
public class SerializationBenchmark {
    private byte[] serialized;
    private Person person;

    @Setup
    public void setup() throws IOException {
        person = new Person("John", 30, "pass123");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
            oos.writeObject(person);
        }
        serialized = bos.toByteArray();
    }

    @Benchmark
    public Object deserializeJava() throws IOException, ClassNotFoundException {
        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(serialized))) {
            return ois.readObject();
        }
    }

    @Benchmark
    public Object deserializeKryo() throws Exception {
        // Assume kryo instance is configured
        Kryo kryo = new Kryo();
        return kryo.readClassAndObject(new com.esotericsoftware.kryo.io.Input(serialized));
    }
}
Production Insight
In a distributed cache layer with 10k+ objects per second, switching from Java serialization to Kryo reduced CPU utilization from 40% to 15% and cut cache size by 60%. The migration required a rolling deploy where old Kryo format was still readable by new code.
Monitor serialization performance with APM tools. If you see GC pressure from serialization buffers, consider external serializers.
But here's the thing: for most applications, the bottleneck is I/O or database, not serialization. Profile before you optimise — don't let the numbers scare you into premature optimisation.
Key Takeaway
Java serialization is 5-10x slower than custom alternatives.
Profile your serialization hotspots before investing in alternative formats.
Hybrid approach: use Java serialization for trusted internal communication, JSON/Protobuf for external.
● Production incidentPOST-MORTEMseverity: high

The 3 AM ClassCastException That Took Down Payment Processing

Symptom
java.io.InvalidClassException: io.thecodeforge.payment.Transaction; local class incompatible: stream classdesc serialVersionUID = 123456789, local class serialVersionUID = 987654321
Assumption
The team assumed that as long as the class name and package were the same, serialized objects from the previous version would deserialize correctly. They didn't explicitly declare serialVersionUID in any class.
Root cause
A minor release changed a private field's type from long to int. Without an explicit serialVersionUID, the JVM computed it from the class structure. The change altered the hash, producing a different UID. Old serialized data could not be deserialized by the new class definition.
Fix
Add an explicit serialVersionUID to every Serializable class. Run a schema migration to convert old data or deploy a backward-compatible version that reads both UIDs.
Key lesson
  • Always declare serialVersionUID explicitly — never rely on JVM computation across versions.
  • Treat serialization as a contract: any class change must be evaluated for backward compatibility.
  • Use integration tests that deserialize old serialized payloads after every deployment.
Production debug guideSymptom → Action guide for the most common serialization issues5 entries
Symptom · 01
InvalidClassException: SerialVersionUID mismatch
Fix
Compare the stream UID (from exception) with the class UID. If different, check git log for class changes. Add explicit serialVersionUID to match the old stream or deploy a compatible version.
Symptom · 02
StreamCorruptedException: invalid stream header
Fix
Check that the byte stream is not truncated or corrupted. Verify the source writes the ObjectOutputStream correctly. Look for concurrent modification of the stream.
Symptom · 03
NotSerializableException thrown at runtime
Fix
Inspect the object graph. Every field must be Serializable or transient. Use serialver utility to list Serializable classes. Mark non-serializable fields as transient or provide custom writeObject.
Symptom · 04
ClassNotFoundException during deserialization
Fix
Ensure the class is available on the classpath of the deserializing JVM. Check for classloader mismatch (e.g., different app servers). Use serialver to confirm the class exists.
Symptom · 05
EOFException while reading ObjectInputStream
Fix
Verify that the number of objects written matches the number read. A common bug: writing one object and reading two, or writing partial data. Wrap writes in try-with-resources to ensure flush and close.
★ Quick Serialization Debug Cheat SheetCommands and immediate fixes for serialization issues in production
InvalidClassException: UID mismatch
Immediate action
Identify the class and compare UIDs using `serialver -classpath <cp> <className>` vs the exception message.
Commands
serialver -classpath target/classes:lib/* io.thecodeforge.payment.Transaction
java -jar check-serial-uid.jar --stream <serialized-file> --classpath target/classes
Fix now
Add private static final long serialVersionUID = <oldUID>L; to the class and redeploy.
StreamCorruptedException: invalid header+
Immediate action
Check that the file exists and is not empty. Use `od -c <file> | head -1` to verify the magic bytes (0xAC, 0xED, 0x00, 0x05).
Commands
od -c /data/sessions/session-2024.dat | head -1
java -jar stream-inspector.jar --file /data/sessions/session-2024.dat
Fix now
Recreate the serialized data from source system. Check for concurrent writes and use synchronization.
NotSerializableException on field+
Immediate action
Identify which field is causing the issue from the stack trace. Change the field type to Serializable or mark it transient.
Commands
grep -rn 'class [A-Z]' src/main/java/ | grep -v 'implements Serializable'
javap -p -c -classpath target/classes io.thecodeforge.model.Invoice | grep -A 5 'writeObject'
Fix now
Add transient modifier to the non-serializable field and implement custom readObject/writeObject to handle it.
ClassNotFoundException on deserialize+
Immediate action
Check classpath on target JVM. Use `java -Djava.ext.dirs=...` to see available classes. Look for different versions of JARs.
Commands
jar tf /opt/app/lib/*.jar | grep -i transaction
javap -classpath /opt/app/lib/*.jar io.thecodeforge.payment.Transaction
Fix now
Ensure the exact version of the class JAR is on the classpath. Consider using a shared classloader or OSGi.
Serialization Options Comparison
FormatThroughput (MB/s)Payload Size (bytes for simple object)Language SupportHuman-readable
Java Serialization50-100~200+Java onlyNo
Externalizable200-300~100Java onlyNo
JSON (Jackson)150-250~120AnyYes
Protocol Buffers400-600~50Any (code gen)No
Kryo300-500~80Java primarilyNo

Key takeaways

1
Serialization converts object graphs to portable byte streams via ObjectOutputStream and ObjectInputStream.
2
Always declare an explicit serialVersionUID to avoid version mismatch breakage across deployments.
3
Externalizable offers 3-5x performance improvement over Serializable but requires manual implementation.
4
Never deserialize untrusted data
use whitelist filters or alternative formats like JSON.
5
Java's built-in serialization is convenient but slow; profile before optimising to custom formats.
6
Use try-with-resources to close ObjectOutputStream/InputStream to prevent resource leaks.

Common mistakes to avoid

7 patterns
×

Not declaring an explicit serialVersionUID

Symptom
After a minor class change (adding a field), deserialization of old data throws InvalidClassException because the JVM-computed UID changed.
Fix
Always add private static final long serialVersionUID = <number>L; to every Serializable class. Use tools like serialver to generate a stable initial UID.
×

Serializing non-Serializable fields without marking them transient

Symptom
NotSerializableException at runtime when serializing an object graph that contains a field whose class does not implement Serializable.
Fix
Mark the field as transient and implement custom serialization (writeObject/readObject) to handle it. Or make the field's class implement Serializable.
×

Deserializing untrusted data without validation

Symptom
Security breach via deserialization gadget chain (e.g., Log4Shell or Commons Collections). Remote code execution.
Fix
Never deserialize data from untrusted sources. Use a whitelist of allowed classes via ObjectInputFilter. Prefer JSON/Protobuf for external input.
×

Assuming serialization is backward-compatible by default

Symptom
After changing a field's type or removing a field, old serialized data cannot be deserialized. ClassCastException or InvalidClassException.
Fix
Explicitly manage version evolution. Keep serialVersionUID the same for compatible changes; add default values in readObject. Use writeObject to write version markers.
×

Not closing ObjectOutputStream/InputStream in try-with-resources

Symptom
Resource leak — file descriptors remain open. In high-concurrency systems, this leads to 'Too many open files' and application crash.
Fix
Always use try-with-resources on both ObjectOutputStream and ObjectInputStream, or ensure proper close() in finally.
×

Forgetting that static fields are not serialized

Symptom
State stored in static fields is lost after deserialization, leading to unexpected null or stale values.
Fix
If you need to preserve static state, save it separately (e.g., in a properties file or thread-local). Consider instance fields instead.
×

Using default serialization for sensitive data

Symptom
Passwords, tokens, or other sensitive fields are serialized in plaintext if not marked transient.
Fix
Mark sensitive fields as transient and implement encryption in custom writeObject/readObject. Or use alternative serialization with field-level encryption.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Explain how Java serialization works internally. What does ObjectOutputS...
Q02SENIOR
What is serialVersionUID and why should you always declare it explicitly...
Q03SENIOR
When would you use Externalizable instead of Serializable? What are the ...
Q04SENIOR
How would you secure your application against deserialization attacks?
Q05SENIOR
What happens if you add a new field to a Serializable class without chan...
Q06SENIOR
How does Java handle circular references during serialization?
Q01 of 06SENIOR

Explain how Java serialization works internally. What does ObjectOutputStream.writeObject() do?

ANSWER
ObjectOutputStream.writeObject() performs a depth-first traversal of the object graph. For each unique object encountered, it writes a class descriptor (fully qualified class name, serialVersionUID, field metadata) followed by the actual field values (using reflection). If the same object appears again, it writes a back-reference handle instead of duplicating the data. The stream uses magic bytes (AC ED 00 05) to identify as Java serialization. WriteObject also handles cycles, inheritance, and transient fields.
FAQ · 7 QUESTIONS

Frequently Asked Questions

01
What is Serialization in Java in simple terms?
02
How do I make a class serializable?
03
Can I serialize static fields?
04
Why do I get InvalidClassException after a minor code change?
05
What are deserialization gadgets?
06
What is the fastest Java serialization library?
07
How do I handle serialization when adding a new field to an existing class?
🔥

That's Java I/O. Mark it forged?

4 min read · try the examples if you haven't

Previous
BufferedReader and BufferedWriter
4 / 8 · Java I/O
Next
NIO in Java