Serialization is the process of converting an object's in-memory state into a stream of bytes, so it can be saved to a file, sent over a network, or stored somewhere for later. Deserialization is the reverse: reading those bytes back and reconstructing an equivalent object.
The Serializable Marker Interface
A class opts into serialization by implementing java.io.Serializable — a marker interface with no methods to implement at all. It simply tells the JVM "objects of this class are allowed to be turned into bytes." Attempting to serialize a class that doesn't implement it throws NotSerializableException.
Writing and reading an object with ObjectOutputStream/ObjectInputStream
Java
import java.io.*;
class User implements Serializable {
private static final long serialVersionUID = 1L;
String username;
transient String sessionToken; // deliberately excluded, see below
User(String username, String sessionToken) {
this.username = username;
this.sessionToken = sessionToken;
}
@Override
public String toString() {
return "User[username=" + username + ", sessionToken=" + sessionToken + "]";
}
}
public class SerializationDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
User original = new User("marcus", "abc-123-secret");
// Serialize: object -> bytes -> file
try (ObjectOutputStream out =
new ObjectOutputStream(new FileOutputStream("user.ser"))) {
out.writeObject(original);
}
// Deserialize: file -> bytes -> object
try (ObjectInputStream in =
new ObjectInputStream(new FileInputStream("user.ser"))) {
User restored = (User) in.readObject();
System.out.println(restored);
}
}
}
User[username=marcus, sessionToken=null]
The transient Keyword
Marking a field transient excludes it from serialization entirely — it's simply skipped when writing the object, and comes back as its default value (null for objects, 0/false for primitives) on the other end. This is exactly what happened to sessionToken above — a good pattern for excluding secrets, caches, or anything that shouldn't (or can't) survive being written to disk.
serialVersionUID and Version Compatibility
Every Serializable class has a serialVersionUID — a version number used during deserialization to confirm that the class definition reading the bytes matches the one that wrote them. If you don't declare it explicitly, the JVM generates one based on the class's structure, which is fragile: adding a field, renaming a method, or even changing compiler versions can silently produce a different generated value.
Always declare serialVersionUID explicitly
Without an explicit serialVersionUID, deserializing an object saved by an older version of the class throws InvalidClassException the moment the class changes shape — even for harmless additions like a new field. Declaring it explicitly (as a private static final long) gives you control over when compatibility actually breaks, instead of leaving it to compiler-generated guesswork.
Modern Alternatives and Security Concerns
Note
Java's built-in serialization mechanism is used far less in modern applications than it used to be. Libraries like Jackson and Gson that serialize to JSON are now the default choice for most applications, because JSON is human-readable, language-agnostic, and much easier to version safely across systems written in different languages.
Deserializing untrusted data is a real security risk
Java's native deserialization has a well-documented history of security vulnerabilities: a malicious byte stream can trigger unexpected code execution during the deserialization process itself, before your code even gets a chance to validate anything. Never call readObject() on data from an untrusted or unauthenticated source. This is one of the strongest practical reasons the industry moved toward JSON-based serialization for data crossing trust boundaries.
Aspect
Java Serialization
JSON (Jackson/Gson)
Format
Java-specific binary
Human-readable, language-agnostic text
Cross-language use
Java only
Any language with a JSON library
Versioning
serialVersionUID, fragile across changes
Flexible field-based mapping
Security track record
History of deserialization exploits
Generally safer, still validate input
Typical use today
Legacy systems, JVM-internal caching
APIs, config, most new development
Tip
For new projects, reach for JSON serialization via Jackson or Gson by default. Reserve Java's native Serializable mechanism for legacy interoperability or narrow, trusted, JVM-internal use cases.
Serialization converts object state to bytes; deserialization reverses it
Serializable is a marker interface with no methods
transient fields are skipped and come back as default values
Always declare serialVersionUID explicitly to control version compatibility
Never deserialize untrusted data — prefer JSON (Jackson/Gson) for most modern use cases