Module 8 · Exceptions, I/O & Resources ⏱ 19 min

Serialization and Data Formats (Java vs JSON)

By the end of this lesson you will be able to:
  • Explain how the Serializable marker interface enables Java native serialization
  • Describe the role of transient and serialVersionUID in controlling serialization
  • Compare Java native serialization with JSON and choose the right format for the context

Most programs need to outlive their process. A game's save file, a shopping cart stored between sessions, or a message sent to another server all require turning an in-memory object into bytes that can be stored or transmitted, then reconstructing it later. That process is called serialization.

Java provides native serialization through the java.io.Serializable marker interface. An ObjectOutputStream walks the object's fields and writes them to a byte stream. An ObjectInputStream reads them back, building a copy of the original object. It is built into the JDK, requires no external libraries, and preserves the full type graph — but it also produces binary data that only Java can read, and it carries security risks that have made it a target for attacks. Because the format is binary, you cannot open a serialized file in a text editor to inspect its contents. That opacity makes debugging harder and interoperability with other languages nearly impossible without complex translation layers.

flowchart LR
  OBJ["Java object"] --> OOS["ObjectOutputStream"]
  OOS --> BYTES["Byte stream"]
  BYTES --> OIS["ObjectInputStream"]
  OIS --> OBJ2["Java object copy"]
  style OOS fill:#c2410c,color:#fff
  style OIS fill:#c2410c,color:#fff
Java native serialization turns an object graph into bytes and back again.

The marker interface and transient fields

Serializable has no methods. It is a marker interface — a promise from the author that the class is safe to serialize. If you try to serialize an object whose class does not implement it, the JVM throws NotSerializableException.

Not every field should survive the trip. A database connection, a cached computed value, or a password should not be written to disk. Mark the field transient and the serialization stream skips it. When the object is deserialized, the transient field is restored to its default value — null for objects, 0 for numbers, false for booleans.

That default-value behaviour can surprise you if you forget a field is transient. A deserialized Player object might show a score of zero not because the player lost, but because the field was never saved — a bug that looks like bad game logic and is actually bad serialization.

The reconstructing code is responsible for rebuilding whatever the transient field held. A common pattern is a custom readObject method that reinitialises caches after the default deserialization finishes. Document transient fields with a comment explaining why they are excluded and how they are rebuilt.

A Serializable class with transient fields. Real, compiling Java.
import java.io.Serializable;

public class Main {
    static class Player implements Serializable {
        private static final long serialVersionUID = 1L;
        String name;
        transient int score;
        transient String sessionToken;

        Player(String name, int score) {
            this.name = name;
            this.score = score;
            this.sessionToken = "secret-" + name;
        }
    }

    public static void main(String[] args) {
        Player p = new Player("Ada", 1000);
        System.out.println("Serializable: " + (p instanceof Serializable));
    }
}

Version mismatches and the UID trap

Java serialization embeds a serialVersionUID — a version fingerprint for the class. If you change the class (add a field, remove a field, change a type) and do not update the UID, deserializing an old file throws InvalidClassException. The JVM is protecting you from silently creating an object whose fields no longer match the class definition.

The fix is simple but easy to forget: declare private static final long serialVersionUID = 1L; and bump it whenever you make an incompatible change. Many developers generate a real UID with the serialver tool instead of using 1L, but the principle is the same.

JSON offers a different path. Libraries like Jackson or Gson turn objects into text that any language can read. There is no serialVersionUID, no binary format to decode, and no class descriptor embedded in the stream. The trade-off is that you must write explicit mapping code and you lose the ability to serialize arbitrary object graphs automatically.

flowchart LR
  SAVE["Save object<br/>serialVersionUID = 1"] --> FILE["File"]
  FILE --> LOAD["Load with class<br/>serialVersionUID = 2"]
  LOAD --> FAIL["InvalidClassException"]
  style FAIL fill:#b45309,color:#fff
Changing the class without updating serialVersionUID breaks deserialization of old data.
JSON is text-based and readable without special tools. Real, compiling Java.
public class Main {
    public static void main(String[] args) {
        String json = "{\"name\":\"Ada\",\"score\":1000}";
        System.out.println("JSON: " + json);
        System.out.println("Readable: " + json.contains("Ada"));
    }
}
flowchart LR
  JS["Java serialization<br/>binary · compact · Java-only"] --> CHOOSE["Choose based on<br/>audience and lifetime"]
  JSON["JSON<br/>text · readable · cross-language"] --> CHOOSE
  style CHOOSE fill:#c2410c,color:#fff
Java serialization trades readability for convenience inside the JVM; JSON trades automatic graph preservation for portability.
Exercise

What does this program print? Read it carefully and type the exact output (two lines).

public class Main {
    static class User implements java.io.Serializable {
        String name;
        User(String name) { this.name = name; }
    }
    static class Guest {
        String name;
        Guest(String name) { this.name = name; }
    }

    public static void main(String[] args) {
        System.out.println(new User("A") instanceof java.io.Serializable);
        System.out.println(new Guest("B") instanceof java.io.Serializable);
    }
}
Exercise

What happens to a transient field when an object is deserialized?

Exercise

What is the primary purpose of serialVersionUID?

Exercise

Which of the following is a key advantage of JSON over Java native serialization?

Exercise

You are designing an API that receives data from untrusted clients over the internet. Which serialization approach is safest?

Recap

  • Serialization turns in-memory objects into bytes for storage or transmission.
  • Serializable is a marker interface with no methods; without it, serialization throws NotSerializableException.
  • transient skips a field during serialization; the field is restored to its default value on deserialization.
  • serialVersionUID guards against version mismatch between the saved bytes and the current class.
  • JSON trades automatic object-graph preservation for human readability and cross-language portability.
  • Avoid Java native serialization for data that crosses trust boundaries; prefer JSON for network APIs.
  • Prefer JSON for APIs and data exchange; reserve Java serialization for short-term storage within a single JVM.

With serialization understood, you now have the full picture of Java's exception, I/O, and resource-management toolkit.

Checkpoint quiz

Which statement about Serializable is true?

Why might a developer choose JSON instead of Java native serialization for a REST API?

Go deeper — technical resources