Serialization in Java

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.

1. What is Serialization and Deserialization?

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.

  • 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

2. Simple use-case

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 then writeObject() method of ObjectOutputStream will throw run-time exception java.io.NotSerializableException.

3. Some complex use-cases

  1. super class is not serializable but sub-class is.
  2. Composed class not supporting Serialization.

3.1. super class is not serializable but sub-class is

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.

3.2. Composed class not supporting Serialization

The only solution for this situation is to mark that field reference as transient and do custom serialization for that.

4. Custom Serialization

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
}

4.1. Custom Serialization example

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() and writeObject() methods, then we need to read (readObject()) in order of writing(writeObject()) in the ObjectOutputStream 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.



Tags: Serialization in Java, Deserialization in Java, ObjectInputStream in Java, ObjectOutputStream in Java, Java

← Back home