What serialization is, serialVersionUID and version mismatches, transient and static fields, non-serializable members, custom writeObject/readObject, circular references, and modern alternatives.
Published September 25, 2026
Serialization questions test both mechanics and judgement. Explain how Java's built-in serialization works, and then show you know why most modern systems use JSON or Protobuf instead, and why deserializing untrusted data is dangerous.
Short answer: Serialization converts an object (and the objects it references) into a byte stream, so it can be stored or sent over a network. Deserialization rebuilds the object from those bytes. In Java, a class opts in by implementing the marker interface java.io.Serializable.
public class Cart implements Serializable {
private static final long serialVersionUID = 1L;
private String userId;
private List<String> skus = new ArrayList<>();
}
try (var out = new ObjectOutputStream(Files.newOutputStream(path))) {
out.writeObject(cart); // serialize
}
try (var in = new ObjectInputStream(Files.newInputStream(path))) {
Cart restored = (Cart) in.readObject(); // deserialize
}
Key points to cover:
serialVersionUID?Short answer: It's a version number for a serializable class. During deserialization, the JVM compares the UID stored in the byte stream with the UID of the class currently loaded. If they differ, it rejects the data with an InvalidClassException, instead of silently producing a corrupt object.
Key points to cover:
private static final long serialVersionUID = 1L;). If you don't, the JVM computes one from the class's structure. Then even a harmless change, such as adding a method, or a different compiler, changes the UID and breaks compatibility with data you've already stored.serialVersionUID changes between serialization and deserialization?Short answer: Deserialization fails with a java.io.InvalidClassException ("local class incompatible: stream classdesc serialVersionUID = X, local class serialVersionUID = Y"). The JVM treats the stored data and the current class as incompatible versions.
Key points to cover:
transient mean?Short answer: Mark them transient. A transient field is skipped during serialization, and gets its default value (null, 0, false) after deserialization.
public class UserSession implements Serializable {
private String userId;
private transient String accessToken; // secret: never written to disk
private transient Map<String, Object> cache; // derived: rebuilt after loading
}
Key points to cover:
transient matters only to Java's built-in serialization. Jackson ignores the keyword by default (it uses @JsonIgnore), and JPA uses @Transient for fields that aren't persisted.Short answer: Only if that field is transient (or null at the time), or if you handle it with custom serialization logic. Otherwise, writeObject throws a java.io.NotSerializableException naming the offending class.
Key points to cover:
writeObject() and readObject() used for?Short answer: They're private hook methods that a serializable class can declare to customise its own serialization. You call defaultWriteObject()/defaultReadObject() for the normal fields, then write or read extra data. Typical uses: handling transient fields, validating on read, or encrypting sensitive data.
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeUTF(encrypt(accessToken)); // custom handling of a transient field
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
accessToken = decrypt(in.readUTF());
if (userId == null) throw new InvalidObjectException("userId required"); // validate on read
}
Common trap: saying these methods are "overridden". They're private methods with an exact signature, which the serialization machinery calls through reflection. They override nothing. (Externalizable is the alternative: you implement writeExternal/readExternal, which give you full control.)
Short answer: No. Serialization captures an object's state, and static fields belong to the class. After deserialization, a static field has whatever value the class currently holds in that JVM.
Short answer: Automatically. ObjectOutputStream keeps a table of the objects already written in the stream. When it meets the same object again, it writes a back-reference (a handle) instead of serializing the object a second time. That prevents infinite recursion, and restores the exact same object graph, including shared references, when deserializing.
class Employee implements Serializable { Department dept; }
class Department implements Serializable { List<Employee> staff = new ArrayList<>(); }
// Employee → Department → staff list → the same Employee: written once, then referenced by handle
Key points to cover:
@JsonManagedReference/@JsonBackReference or @JsonIgnore, or map to DTOs.Q: Why is Java deserialization considered a security risk?
A: Deserializing untrusted bytes can instantiate arbitrary classes on the classpath and trigger code in their readObject methods. That has led to remote-code-execution exploits ("gadget chains"). Never deserialize untrusted input with ObjectInputStream. If you must, use serialization filters (ObjectInputFilter, Java 9+) to allow-list the permitted classes.
Q: What do modern applications use instead of Java serialization? A: Language-neutral formats: JSON (Jackson) for APIs, Protobuf or Avro for compact, schema-evolving messages (Kafka, gRPC), and Kryo for fast JVM-only caching. Records work well with all of them.
Q: Serializable vs Externalizable?
A: Serializable is automatic, with optional hooks. Externalizable requires you to write and read every field yourself, and it needs a public no-argument constructor. It's faster and fully controlled, but more work and more error-prone.
Q: How do you keep a singleton a singleton after deserialization?
A: Implement readResolve() to return the existing INSTANCE, or use an enum singleton, which the serialization machinery handles correctly.