Study guide
11.4 Java Object Serialization and Record Serialization
Oracle Java SE 21 Developer 1Z0-830
Track your progress as you read
Finished sections get a check mark.
1 Chapter 1: Java Basics, Primitive Types, and Operators
1.1 Program Structure, Primitives, Literals, Scopes, and var 1.2 Operators, Precedence, and Type Casting 1.3 Strings, Text Blocks, and StringBuilder 1.4 Date and Time APIs (java.time)
2 Chapter 2: Controlling Program Flow & Pattern Matching
2.1 Conditional Logic: if-else and Ternary Operators 2.2 Switch Statements and Switch Expressions 2.3 Pattern Matching for switch and instanceof 2.4 Loops and Branching Statements
3 Chapter 3: Object-Oriented Design & Class Architecture
3.1 Class Declarations, Constructors, and Object Lifecycle 3.2 Encapsulation and Access Modifiers 3.3 Inheritance, Method Overriding, and Polymorphism 3.4 Abstract Classes and Interfaces 3.5 Nested, Inner, Local, and Anonymous Classes 3.6 Method Overloading, Overload Resolution, and Varargs
4 Chapter 4: Modern Java Data Modeling: Records, Enums, and Sealed Types
4.1 Java Records, Canonical/Compact Constructors, and Component Accessors 4.2 Java Enums: Fields, Methods, and Abstract Constant-Specific Behavior 4.3 Sealed Classes and Interfaces: permits, final, non-sealed, and Hierarchy Architecture 4.4 Advanced Record Patterns (JEP 440), Nested Deconstruction, and Sealed Hierarchy Exhaustiveness
5 Chapter 5: Exception Handling and Annotations
5.1 Exception Hierarchy: Checked vs. Unchecked Exceptions 5.2 Exception Handling: try, catch, finally, and Multi-Catch 5.3 Resource Management: try-with-resources and AutoCloseable 5.4 Custom Exceptions and Best Practices 5.5 Java Annotations: Built-in, Meta-Annotations, and Custom Types
6 Chapter 6: Generics and the Collections Framework
6.1 Generics Fundamentals and Generic Methods 6.2 Wildcards, Type Bounds, and Type Erasure 6.3 Core Collections: Lists, Sets, Queues, and Maps 6.4 Sequenced Collections in Java 21 6.5 Sorting, Comparable, and Comparator 6.6 Arrays: Creation, Multidimensional Forms, and the Arrays Utility Class
7 Chapter 7: Functional Programming & Lambda Expressions
7.1 Built-in Functional Interfaces in java.util.function 7.2 Lambda Expressions, Variable Scopes, and Effectively Final 7.3 Method References: Four Varieties and Edge Cases 7.4 Working with java.util.Optional
8 Chapter 8: The Stream API and Data Pipelines
8.1 Stream Creation and Intermediate Operations 8.2 Terminal Operations and Reductions 8.3 Advanced Collectors, Grouping, Partitioning, and Teeing 8.4 Primitive Streams and Parallel Stream Processing
9 Chapter 9: Concurrency, Multithreading, and Virtual Threads
9.1 Thread Fundamentals, Creation, and Lifecycle 9.2 Thread Safety, Synchronization, and the Java Memory Model 9.3 Concurrency Utilities: ExecutorService, Callable, and Future 9.4 Concurrent Collections, Atomics, and Explicit Locks 9.5 Virtual Threads in Java 21 (Carrier Threads, Pinning, and Best Practices)
10 Chapter 10: The Java Platform Module System (JPMS)
10.1 Module Architecture and module-info.java Declarations 10.2 Module Directives: requires, exports, opens, provides, and uses 10.3 Modular Compilation, Packaging, and CLI Execution 10.4 Module Types, Migration Strategies, ServiceLoader SPI, and JLink
11 Chapter 11: Java I/O, NIO.2, and Object Serialization
11.1 Classic Java I/O Streams and Readers/Writers 11.2 Java NIO.2 File API: Path, Paths, and File System Operations 11.3 Advanced NIO.2 Stream Operations and Directory Traversal 11.4 Java Object Serialization and Record Serialization
12 Chapter 12: Implementing Localization and Internationalization
12.1 Locales and Resource Bundles 12.2 Formatting and Parsing Numbers, Currencies, Dates, Times, and Messages
11.4 Java Object Serialization and Record Serialization
Key Takeaways Explain This Topic AI
Object serialization converts object graphs into byte streams via ObjectOutputStream and deserializes them via ObjectInputStream, requiring participating classes to implement the Serializable marker interface.
Fields marked transient and static are excluded from serialization; upon standard class deserialization, transient fields are initialized to primitive default values or null without executing inline initializers.
Deserialization of ordinary classes bypasses the serializable class's constructors and only invokes the no-argument constructor of the first non-serializable superclass, requiring an accessible no-arg constructor to avoid InvalidClassException.
Custom serialization hooks (writeObject, readObject, readResolve, writeReplace) allow tailored data representation and singleton identity preservation in standard classes.
Record serialization in Java SE 21 bypasses custom writeObject/readObject methods and unconditionally reconstructs record instances through their canonical constructor, guaranteeing that all validation rules and invariants are strictly enforced.
Last updated: September 2026
Java Object Serialization and Record Serialization
Serialization is the mechanism of transforming an in-memory Java object graph into a sequential stream of binary bytes for persistent storage on disk or transmission across network boundaries. Deserialization performs the reverse transformation, reconstructing a live, active Java object graph on the JVM heap from the serialized byte stream.
On the Oracle Certified Professional: Java SE 21 Developer (1Z0-830) exam, serialization is a heavily tested architectural topic. Candidates must master the java.io.Serializable marker interface, serialVersionUID evolution rules, transient and static field behavior, constructor invocation semantics during standard class deserialization, custom serialization hooks ( writeObject , readObject , readResolve , writeReplace ), the Externalizable interface, and the modern serialization protocol for Java records .
1. The Serialization Protocol & Serializable
To enable serialization for a class, it must implement the java.io.Serializable interface. Serializable is a marker interface (it declares no methods or constants), signaling to the JVM that instances of the class are safe to serialize. public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
- private String name;
- private double salary;
- private transient String secretPin; // Excluded from serialization
- public Employee(String name, double salary, String pin) {
- this.name = name;
- this.salary = salary;
- this.secretPin = pin;
- }
- public String getName() { return name; }
- public double getSalary() { return salary; }
- public String getSecretPin() { return secretPin; }
- }
- Serializing and Deserializing Objects
Object serialization is executed via java.io.ObjectOutputStream and java.io.ObjectInputStream : // Writing an object graph to a binary file:
- try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.ser"))) {
- Employee emp = new Employee("Alice Johnson", 95000.0, "9876");
- oos.writeObject(emp);
- }
// Reading and reconstructing the object graph from the binary file:
- try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("employee.ser"))) {
- Employee emp = (Employee) ois.readObject();
- System.out.println("Name: " + emp.getName()); // "Alice Johnson"
- System.out.println("Salary: " + emp.getSalary()); // 95000.0
// Transient field receives primitive/reference default value:
- System.out.println("PIN: " + emp.getSecretPin()); // null
- }
- The NotSerializableException
Warning
If an object implementing Serializable contains a reference to an object that does not implement Serializable , invoking ObjectOutputStream.writeObject() throws java.io.NotSerializableException at runtime! To prevent this exception, non-serializable fields must be explicitly marked with the transient keyword or must hold a null reference at the time of serialization.
2. Field Modifiers: transient and static
static Fields : Belong to the class definition rather than any individual object instance. Static fields are never serialized . When an object is deserialized, static fields reflect whatever value is currently loaded in the target JVM's class definition.
transient Fields : Explicitly excluded from the serialized stream. They are used for sensitive credentials (passwords, cryptographic keys), temporary cache buffers, or non-serializable system resources (such as database connections or thread handles).
The Transient Default Initialization Trap
When an ordinary Serializable class is deserialized:
transient reference types are initialized to null .
transient numeric primitives ( byte , short , int , long , float , double ) are initialized to 0 ( 0.0 ).
transient boolean fields are initialized to false .
transient char fields are initialized to '\u0000' .
Important
Inline Field Initializers Do NOT Run : During standard class deserialization, inline field initializers (e.g., private transient int maxAttempts = 5; ) and instance initializer blocks are NOT executed . The transient field will have the default value 0 , not 5 !
3. Class Evolution and serialVersionUID
serialVersionUID is a 64-bit hash ( long ) used as a version identifier for a Serializable class: private static final long serialVersionUID = 1L;
Why Declare serialVersionUID Explicitly?
If a Serializable class does not declare an explicit serialVersionUID , the Java compiler automatically computes a default UID based on the class name, implemented interfaces, fields, and method signatures. Any subsequent change to the source code (such as adding a private helper method or reordering fields) changes the generated UID hash.
InvalidClassException
During deserialization, ObjectInputStream compares the serialVersionUID stored in the serialized byte stream with the serialVersionUID of the class currently loaded in the JVM. If the two identifiers do not match, deserialization immediately aborts by throwing java.io.InvalidClassException .
4. Object Deserialization & Constructor Invocation Rules
One of the most frequently tested topics on the 1Z0-830 exam is the exact sequence of constructor invocations during deserialization of standard classes:
The 4 Golden Deserialization Rules for Standard Classes
Constructors of Serializable classes are NEVER executed during deserialization! The JVM allocates raw uninitialized heap memory for the object and injects field values directly from the stream using internal reflection.
The JVM traverses up the class inheritance hierarchy to locate the first NON-SERIALIZABLE superclass.
The JVM invokes the NO-ARGUMENT constructor of that first non-serializable superclass.
If that first non-serializable superclass does not declare an accessible no-argument constructor (for example, if it only defines parameterized constructors), the JVM throws java.io.InvalidClassException at runtime during deserialization! // Non-serializable superclass MUST have an accessible no-arg constructor:
- public class Person {
- private String species;
public Person() { // Required for subclass deserialization!
- this.species = "Homo sapiens";
- System.out.println("Person no-arg constructor executed");
- }
- public Person(String species) {
- this.species = species;
- }
- }
- public class Manager extends Person implements Serializable {
- private static final long serialVersionUID = 1L;
- private String department;
- public Manager(String dept) {
- super("Human");
- this.department = dept;
- System.out.println("Manager constructor executed");
- }
- }
// When deserializing a serialized Manager object:
// 1. Person() no-arg constructor IS executed!
// 2. Manager(String) constructor IS NOT executed!
5. Custom Serialization Hooks for Standard Classes
Standard classes can customize their serialization and deserialization formats by declaring special private methods with exact signatures: public class SecureAccount implements Serializable {
private static final long serialVersionUID = 1L;
- private String accountNumber;
- private transient String password;
- // Custom serialization method
- private void writeObject(ObjectOutputStream out) throws IOException {
- out.defaultWriteObject(); // Serializes all non-transient fields normally
// Write custom encrypted/masked representation of transient field:
- String encryptedPass = encrypt(this.password);
- out.writeUTF(encryptedPass);
- }
- // Custom deserialization method
- private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
- in.defaultReadObject(); // Deserializes all non-transient fields normally
// Read and restore custom encrypted transient field:
- String encryptedPass = in.readUTF();
- this.password = decrypt(encryptedPass);
- }
- // Invoked if class hierarchy has changed and stream has no data for this class
- private void readObjectNoData() throws ObjectStreamException {
- this.password = "DEFAULT_PASSWORD";
- }
- // Designates an alternate object to be serialized in place of this object
- private Object writeReplace() throws ObjectStreamException {
- return new AccountProxy(this.accountNumber);
- }
- // Replaces the deserialized object with an existing instance (e.g. Singleton Pattern)
- private Object readResolve() throws ObjectStreamException {
- return AccountRegistry.getCanonicalInstance(this.accountNumber);
- }
- private String encrypt(String raw) { return raw != null ? "ENC:" + raw : ""; }
- private String decrypt(String enc) { return enc.startsWith("ENC:") ? enc.substring(4) : enc; }
- }
- The Role of readResolve() in the Singleton Pattern
When a singleton instance is serialized and subsequently deserialized via readObject() , the JVM standard deserializer creates a new, distinct instance on the heap, violating the core invariant of the Singleton design pattern. Implementing readResolve() allows the class to discard the newly deserialized instance and return the existing singleton reference instead: public class DatabasePool implements Serializable {
- private static final long serialVersionUID = 1L;
- private static final DatabasePool INSTANCE = new DatabasePool();
- private DatabasePool() {}
- public static DatabasePool getInstance() { return INSTANCE; }
// Preserves Singleton Identity during Deserialization:
- private Object readResolve() throws ObjectStreamException {
- return INSTANCE;
- }
- }
- 6. The java.io.Externalizable Interface
Externalizable is a sub-interface of Serializable that provides complete, manual control over the binary format: public interface Externalizable extends Serializable {
- void writeExternal(ObjectOutput out) throws IOException;
- void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
- }
- Critical Exam Rules for Externalizable
Requires Public No-Arg Constructor : Unlike standard Serializable classes (where constructors are bypassed), an Externalizable class MUST declare a public no-argument constructor .
Constructor Invocation : During deserialization, the JVM first invokes the public no-argument constructor of the externalizable class itself, and then calls readExternal(in) on that newly created instance.
No Automatic Field Storage : The JVM does not automatically write any field data. All fields (even non-transient ones) must be explicitly written in writeExternal() and read in readExternal() in identical order.
7. Modern Record Serialization in Java SE 21
Java records introduce a completely redesigned, secure serialization protocol that fundamentally differs from standard class serialization. public record CustomerOrder(String orderId, double amount, int itemCount) implements Serializable {
// Compact constructor enforces validation invariants:
- public CustomerOrder {
- Objects.requireNonNull(orderId, "orderId cannot be null");
- if (amount < 0.0) {
- throw new IllegalArgumentException("Order amount cannot be negative");
- }
- if (itemCount <= 0) {
- throw new IllegalArgumentException("Item count must be positive");
- }
- }
- }
- The 5 Core Rules of Record Serialization
Always Invokes the Canonical Constructor : Unlike standard classes (which bypass constructors), record deserialization ALWAYS invokes the canonical constructor , passing the deserialized stream components as arguments.
Strict Invariant and Validation Enforcement : Because the canonical constructor is guaranteed to execute, any validation checks, sanitization logic, or defensive copies defined in compact/canonical constructors are strictly enforced during deserialization . Tampered or malicious serialized byte streams cannot bypass record integrity constraints!
Custom Methods are Completely Ignored : Declaring writeObject() , readObject() , readObjectNoData() , or serialPersistentFields inside a record has zero effect . The JVM serialization framework completely ignores them.
serialVersionUID Defaults to 0L : The serialVersionUID requirement is waived for records. The default UID is 0L , and changes in component order or adding/removing record components do not trigger InvalidClassException based on UID mismatch.
readResolve and writeReplace Supported : Records support writeReplace() and readResolve() (for instance, to substitute a proxy or canonical cached instance).
8. Serialization Architecture Comparison Matrix
Feature Standard Class ( Serializable ) Externalizable Class Record ( Record implements Serializable )
Deserialization Mechanism Field injection via reflection readExternal() callback Canonical Constructor Invocation
Constructor Executed First non-serializable superclass no-arg Class's own public no-arg constructor Record's Canonical Constructor
- Validation Enforcement Bypassed by default Bypassed (inside readExternal ) Strictly Enforced by Constructor
- writeObject / readObject Supported Ignored (uses writeExternal ) Completely Ignored
- readResolve / writeReplace Supported Supported Supported
- serialVersionUID Check Enforced (UID mismatch throws exception) Enforced Waived (Defaults to 0L )
- transient Keyword Excludes field N/A (all serialization is manual) Not permitted on record components