Handling Non-Serializable Parts in Java Serialization
Serialization in Java allows you to convert an object into a byte stream so that it can be stored or transferred and later reconstructed. However, real-world objects often contain parts that should not or cannot be serialized directly. Java provides mechanisms like transient fields and custom serialization to handle these cases. Let us delve into understanding how Java handles non-serializable parts during serialization and how they can be managed effectively.
1. What is Java Serialization?
Java serialization is the process of converting an object’s state into a byte stream so that it can be stored or transferred. In Java, this mechanism is enabled when a class implements the Serializable interface. Serialization is widely used in distributed systems, caching, deep cloning, and persistence mechanisms.
During deserialization, the byte stream is converted back into a fully reconstructed Java object. This round-trip mechanism works automatically, but Java also provides several hooks to customize the behavior, especially when dealing with objects that contain non-serializable parts.
1.1 What are Transient Fields?
A transient field is a variable that is intentionally excluded from serialization. When an object is serialized, the values of its transient fields are not written to the output stream. This is useful when:
- the field holds sensitive information (e.g., passwords)
- the field references non-serializable objects
- the field value can be recalculated after deserialization
- the field is resource-heavy (e.g., open files, network connections)
1.2 What is Custom Serialization?
Custom serialization gives developers complete control over what gets written to or restored from the serialized form. This is done by defining the following private methods inside the class:
private void writeObject(ObjectOutputStream oos) throws IOException; private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException;
These methods override the default serialization behavior. They are especially helpful when handling non serializable parts during serialization — for example, when a class contains an object that does not implement Serializable, but still needs to be reconstructed in some way.
2. Code Example
The following example shows how to handle java serialization when a class contains non-serializable parts using custom readObject and writeObject methods.
// SerializationDemo.java
import java.io.*;
class NonSerializableService {
private String serviceName;
public NonSerializableService(String serviceName) {
this.serviceName = serviceName;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
}
class User implements Serializable {
private String username;
private transient NonSerializableService service; // cannot be serialized normally
private int sessionCount;
public User(String username, int sessionCount, String serviceName) {
this.username = username;
this.sessionCount = sessionCount;
this.service = new NonSerializableService(serviceName);
}
// Custom serialization
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
// Serialize the essential state of the non-serializable object
out.writeUTF(service.getServiceName());
}
// Custom deserialization
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
String restoredServiceName = in.readUTF();
// Reconstruct the non-serializable service using the stored state
this.service = new NonSerializableService(restoredServiceName);
System.out.println("Restored service: " + restoredServiceName);
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", sessionCount=" + sessionCount +
", serviceName=" + service.getServiceName() +
'}';
}
}
public class SerializationDemo {
public static void main(String[] args) throws Exception {
User user = new User("yb", 5, "EmailService");
// Serialize
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.data"));
oos.writeObject(user);
oos.close();
// Deserialize
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.data"));
User restoredUser = (User) ois.readObject();
ois.close();
System.out.println(restoredUser);
}
}
2.1 Code Explanation
The updated code demonstrates custom Java serialization when a serializable class contains a non-serializable component whose state must still be preserved; the User class implements Serializable and includes a transient field referencing NonSerializableService, which now properly stores a serviceName as state. During serialization, the writeObject() method first invokes defaultWriteObject() to serialize the regular fields (username and sessionCount), then manually writes the essential state of the non-serializable service—its serviceName—using writeUTF(). During deserialization, readObject() restores the regular fields using defaultReadObject(), reads back the serialized service name, and reconstructs the non-serializable service by creating a new NonSerializableService instance initialized with the restored name, printing it for clarity. Finally, the SerializationDemo class serializes a User object to a file, deserializes it, and prints the reconstructed object, demonstrating how non-serializable components can be safely rebuilt using their persisted state.
2.2 Code Output
The following output shows how the service name is restored during custom deserialization and how the reconstructed User object is printed.
Restored service: EmailService
User{username='yb', sessionCount=5, serviceName=EmailService}
The output shows the message Restored service: EmailService, which is printed during the custom deserialization process after the serialized service name is read back and used to reconstruct a new NonSerializableService instance, confirming that the non-serializable component has been successfully restored using its preserved state; the next line prints the fully reconstructed User object as User{username='yb', sessionCount=5, serviceName=EmailService}, demonstrating that all regular fields were correctly deserialized and that the transient service field, though not serialized directly, has been rebuilt accurately with the original service name.
3. Conclusion
When dealing with non-serializable components inside serializable classes, Java gives you flexible tools like transient fields and custom serialization. This allows you to serialize only what matters and safely reconstruct the rest, making your object serialization logic robust and adaptable.

