We will start with what Serialization in Java is and then showcase one basic example on how serialization works in Java and then jumps into complex use cases.
Serialization is the process of encoding an object to byte stream and reverse of it is called deserialization. It is platform independent process which means you can serialize the object in one JVM and can transport the object over network and/ or store it in file system and then deserialize it in other or on same JVM.
Class needs to implements marker interface
Serializable
in order to make their object to be eligible for Serialization.fields with transient and/or _static modifiers are not serialized by regular serialization process.
ObjectInputStream
andObjectOutputStream
are high level stream classes which have methods to serialize and de-serialize Java objects.
ObjectOutputStream
class has many write methods but the method that is usually used method is:public final void writeObject(Object obj) throws IOException
ObjectInputStream
class has many read methods but the method that is usually used method is:public final Object readObject() throws IOException, ClassNotFoundException
Let's take an example of a simple class with mostly primitive or inbuilt data structures. For this example we are creating Dog
class with two member variables of type int
and String
.
public class Dog implements Serializable {
private static final long serialVersionUID = 8661314562327474362L;
private int height;
private String name;
// getters/ setters/ toString/ constructors omitted
}
Now, let's use ObjectOutputStream
and ObjectInputStream
to serialize and deserialize the object of Dog
class.
public class SimpleSerializationExample {
public static void main(String[] args) {
Dog dog = new Dog(50, "Titan");
System.out.println("Before Serialization");
System.out.println(dog);
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("dog.ser"))) {
oos.writeObject(dog);// serialize the dog object
}
catch (IOException ioEx) { /* Don't Swallow exception in real projects */ }
dog = null; // clear old dog object reference
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("dog.ser"))) {
dog = (Dog) ois.readObject();// deserialize dog object
}
catch (IOException | ClassCastException | ClassNotFoundException ex) { /* Don't Swallow exception in real projects */ }
System.out.println("After Serialization");
System.out.println(dog);
}
}
Point to remember
If the class is not implementing
Serializable
interface thenwriteObject()
method ofObjectOutputStream
will throw run-time exceptionjava.io.NotSerializableException
.
There could be a situation when you are extending some class which is not serializable but you want the sub class to be serializable.
If the super class doesn't implements Serializable
interface then the inherited fields in the sub class will be initialized to their default values and the constructor will be called from first instance of non-serializable class till all the super classes constructors are run. But in case of Serializable
class, constructor will never run at the time of deserialization.
The only solution for this situation is to mark that field reference as transient and do custom serialization for that.
writeObject
and readObject
to rescue for 3.1. and 3.2.
Java serialization have special mechanism just for this. It has a set of private methods you can implement in your serializable class which will be invoked during serilization_ and _deserilization. The signature of these methods are as follows.
private void writeObject(ObjectOutputStream os) {
//Your custom code for serilization goes here
}
private void readObject(ObjectInputStream is) {
//Your custom code for deserilization goes here
}
Let's extend the previous example and composed Dog
class with Collar
class. This newly added Collar
class doesn't implements Serializable
interface. We will implements writeObject
and readObject
in Dog
class just to handle serialization of this transient field.
public class Collar {
private int size;
// getters/ setters/ toString
}
public class Dog implements Serializable {
private static final long serialVersionUID = 6870143058315212650L;
private int height;
private String name;
private transient Collar myCollar;
//getters/ setters/ toString
private void writeObject(ObjectOutputStream os) throws IOException {
try {
os.defaultWriteObject();// lets first have default serialization happens
os.writeInt(myCollar.getSize()); // save custom values
} catch (IOException ex) { }
}
private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {
try {
is.defaultReadObject();// lets first have default deserialization happens
myCollar = new Collar(is.readInt()); // custom read values
} catch (IOException | ClassNotFoundException ex) { }
}
}
Point to remember
If we are customizing the serialization and deserialization using
readObject()
andwriteObject()
methods, then we need to read (readObject()) in order of writing(writeObject()) in theObjectOutputStream
because it saves in the sequence we write it. If we don't do so, we may end up in half-baked or wrongly deserialized object.