Core Java ยท 9. More core APIs, lesson 3 of 6

Serialization: Serializable, transient and safer alternatives

Intermediate3 min read@since 16Code runs on your Java 25
Explain it forThe essentials plus production detail and pitfalls.

Serialization turns an object into bytes (to save to a file or send over a network); deserialization turns the bytes back into an object.

  • A class opts in by implementing the marker interface Serializable.
  • ObjectOutputStream.writeObject() writes the object and everything it references; ObjectInputStream.readObject() rebuilds it.
  • transient fields are skipped (passwords, caches, connections) and come back as default values. static fields aren't serialized either.
  • serialVersionUID identifies the class version. If the stored ID doesn't match the class, deserialization fails with InvalidClassException, so declare it explicitly.
  • The class's own constructors don't run during deserialization.

Java serialization is now considered risky and legacy: deserializing untrusted bytes can run attacker-controlled code ("gadget chains"). For new code prefer JSON (Jackson), Protocol Buffers or another explicit format. If you must use it, install a deserialization filter (ObjectInputFilter).

Diagram

Example

Java
record Address(String city, String pin) implements Serializable {}

class User implements Serializable {
    @Serial private static final long serialVersionUID = 1L;   // @Serial: Java 14+

    private final String email;
    private final Address address;                    // must be Serializable too
    private transient String sessionToken;            // never written

    User(String email, Address address, String token) {
        this.email = email; this.address = address; this.sessionToken = token;
    }
}

User asha = new User("asha@example.com", new Address("Pune", "411001"), "secret-123");

try (var out = new ObjectOutputStream(new FileOutputStream("user.bin"))) {
    out.writeObject(asha);
}

try (var in = new ObjectInputStream(new FileInputStream("user.bin"))) {
    in.setObjectInputFilter(ObjectInputFilter.Config.createFilter("com.shop.*;java.base/*;!*"));   // allow-list
    User copy = (User) in.readObject();               // sessionToken is null in the copy
}

Common mistake

Deserializing data from users, files or the network with a plain ObjectInputStream. That has been the root cause of many remote-code-execution vulnerabilities.

Under the hood

Records serialize more safely than ordinary classes: deserialization goes through the canonical constructor, so its validation runs. Externalizable gives full manual control. In distributed systems, a stable, versioned schema (JSON with explicit fields, Avro, Protobuf) avoids the tight coupling of Java serialization, where renaming a private field can break stored data.

Check yourself

Which field is NOT written by ObjectOutputStream?

How this connects

Where this leads

You've reached the end of this thread. Try a learning path for what's next.

Was this lesson helpful?

Finished reading? Mark it complete to track your progress.