Preface: Java serialization has both performance and security implications. While it allows any object to be converted into a byte stream, alternatives like Protocol Buffers and Jackson JSON are often preferred for various reasons.
For information on how JSON serialization works, check out Java Object Mapper, What it is, how it works.
Java is an object-oriented language. You define classes like this:
public class User {
private String name;
private String email;
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
and create objects like this:
User user = new User();
user.setName("Sam");
user.getName(); //Sam
While the Java runtime (JVM) understands this structure, other environments may not. What if you want to save a user to a database? What if you want to write that user to a file or transfer it over a network?
Serialization makes this possible by converting a Java class into a series of bytes that another system can reconstruct into the original object. The process of writing an object to a byte sequence is serialization, while reading those bytes back into an object is deserialization.
Key Takeaways
- Serialization converts Java objects into byte streams for storage or transmission.
- Java's
Serializableinterface marks classes for serialization. serialVersionUIDensures class compatibility during deserialization.- Serialization involves security and performance trade-offs.
- Alternatives to Java's serialization include Protocol Buffers and JSON.
What is serialization?
Serialization involves converting Java objects into data formats that other systems can interpret. For instance, a database understands byte sequences rather than object instantiations like this:
new User()
A byte representation might look like this:
0000000 edac 0500 7273 1500 6f63 2e6d 7865 6d61
0000010 6c70 2e65 6564 6f6d 552e 6573 6372 ca34
0000020 73ee 0345 029d 0200 004c 7008 7361 7773
0000030 726f 7464 1200 6a4c 7661 2f61 616c 676e
0000040 532f 7274 6e69 3b67 004c 7508 6573 6e72
0000050 6d61 7165 7e00 0100 7078 7070
000005c
Serialization isn't unique to Java—languages like JavaScript and PHP use it too. Here's how JavaScript handles JSON deserialization:
var obj = JSON.parse('{ "name":"Sam", "email":"sam@mail.com"}');
Why do we need serialization in Java?
Serialization is crucial for handling data transfer across diverse systems. Without it, data transmission from a web server to a web browser, or from a Java entity to a database, wouldn't be feasible.
It's the bridge between the POJOs you define and their stored or transmitted representations. Serialization enables system interoperability, allowing data to be preserved and shared effectively.
Serialization in Java: How it works
The java.io package offers classes to serialize objects, like ObjectOutputStream:
FileOutputStream fos = new FileOutputStream("temp.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User user = new User();
oos.writeObject(user);
oos.flush();
oos.close();
Here's the serialization process:
- Metadata about the object's class and its superclasses is serialized.
- Non-static, non-transient instance data is serialized.
- Members' metadata is serialized, similar to step 1.
- Members' data is serialized, similar to step 2.
ObjectOutputStream uses FileOutputStream to handle stream data. For more on Java I/O basics, see FileReader vs BufferedReader vs Scanner.
Deserialization uses ObjectInputStream:
FileInputStream fis = new FileInputStream("temp.out");
ObjectInputStream oin = new ObjectInputStream(fis);
try {
User user = (User) oin.readObject();
} catch(Exception e) {
//handle exception
}
The deserialization process reads metadata to reconstruct the object accurately.
The Java Serializable Interface
The User class must implement Serializable:
public class User implements Serializable
If not, you'll see this exception:
Exception in thread "main" java.io.NotSerializableException: com.example.demo.User
Implementing Serializable indicates a class can be serialized. It contains no methods—it's a marker interface. Members of a class, such as:
public class User {
private Address address;
}
must also be serializable for the entire object to be successfully serialized.
SerialVersionUID
Each Serializable class has a unique identifier for versioning, known as serialVersionUID:
public class User implements Serializable {
private static final long serialVersionUID = 7148561634028749725L;
...
Defining this explicitly is critical to avoid inconsistencies across different Java implementations.
Problems with Serialization?
Serialization can expose private data through unintended access to non-transient private members. Without explicit serialVersionUID, changes in class definitions over time can lead to deserialization issues.
Although Java's serialization is versatile, alternatives like Protocol Buffers or Jackson can offer better solutions depending on your use case.
Java Serialization Example
package com.example.demo;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 7148561634028749725L;
private String username;
private transient String password;
public void setUserName(String username){
this.username = username;
}
public String getUserName(){
return this.username;
}
public void setPassword(String password){
this.password = password;
}
public String getPassword(){
return this.password;
}
}
package com.example.demo;
import java.io.*;
public class DemoApplication {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("temp.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User user = new User();
user.setUserName("Sam");
user.setPassword(("password123"));
oos.writeObject(user);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("temp.out");
ObjectInputStream oin = new ObjectInputStream(fis);
try {
User userFromFile = (User) oin.readObject();
System.out.println(userFromFile.getUserName()); //Sam
System.out.println(userFromFile.getPassword()); //null because transient field isn't serialized.
} catch(Exception e) {
//handle exception
}
}
}
The User class implements the Serializable interface with a serialVersionUID, while the transient keyword prevents password from being serialized, resulting in null during deserialization.
FAQ
Why is serialVersionUID important?
It ensures that a serialized object corresponds to a serialized class. Without defining it, changes in class details can cause deserialization errors.
Can serialization be a security risk?
Yes, serialized data can be tampered with, leading to deserialization vulnerabilities. Use alternatives or implement security checks where needed.
When should I use alternatives to Java serialization?
Consider alternatives like Protocol Buffers when efficiency, cross-language communication, or forward/backward compatibility is crucial.
What does transient mean?
The transient keyword prevents fields from being serialized, useful for sensitive data or non-essential fields.
