Java Serialization and Deserialization


Introduction & Definitions

Serialization

Serialization is the translation of your Java object’s values/states to bytes to send it over network or save it.

Deserialization

Deserialization is conversion of byte code to corresponding java objects.

Note:

Good thing about Serialization is entire process is JVM independent, meaning that an object can be serialized on one platform and deserialized on an entirely different platform.

Rules

  • If you want to serialize any class then it must implement Serializable interface which is marker interface.

    Marker interface in Java is interface with no field or methods or in simple word empty interface in java is called marker interface

  • When you serialize any object and if it contains any other object reference then Java serialization serialize that object’s entire object graph.

Steps for Serialization

Step 1 : Make your class implements Serializable interface
Step 2 : Create OutputSteam object with target( If you are writing to file then FileOutputStream with file name to which you want to write).
Step 3 : Create ObjectOutputStream with using OutputSteam created in step 2.
Step 4 : Call writeObject method on ObjectOutputStream object.

Ex:

Employee.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package concepts.serialization.bean;
import java.io.Serializable;
/* Step 1 : Make your class implements Serializable interface */
public class Employee implements Serializable {
	private String EmpName;
	private Integer EmpId;
	private Integer basic;
	
	public Employee(String empName, Integer empId, Integer basic) {
		super();
		EmpName = empName;
		EmpId = empId;
		this.basic = basic;
	}	
	public String getEmpName() {
		return EmpName;
	}
	public void setEmpName(String empName) {
		EmpName = empName;
	}
	public Integer getEmpId() {
		return EmpId;
	}
	public void setEmpId(Integer empId) {
		EmpId = empId;
	}
	public Integer getBasic() {
		return basic;
	}
	public void setBasic(Integer basic) {
		this.basic = basic;
	}
}

SerializationDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package concepts.serialization.serialization;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

import concepts.serialization.bean.Employee;

public class SerializationDemo {

	public static void main(String[] args) {
		Employee emp = new Employee("Deepak", 100, 10000);

		try {
			/* Step 2: Create OutputSteam object with target */
			FileOutputStream fileOut = new FileOutputStream(new File("employee.ser"));
			/*Step 3 : Create ObjectOutputStream with using OutputSteam created in step 2 */
			ObjectOutputStream outStream = new ObjectOutputStream(fileOut);
			/*Step 4 : Call writeObject method on ObjectOutputStream object */
			outStream.writeObject(emp);
			outStream.close();
			fileOut.close();
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}
}

Steps for Deserialization

Step 1 : Make your class implements Serializable interface
Step 2 : Create InputSteam object with source( If you are reading from file then FileInputStream with file name to which you want read from).
Step 3 : Create ObjectInputStream with using InputSteam created in step 2.
Step 4 : Call readObject method on ObjectInputStream object.

Ex:

DeserializationDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package concepts.serialization.serialization;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

import concepts.serialization.bean.Employee;

public class DeserializationDemo {

	public static void main(String[] args) {
		Employee emp = null;
		try {
			/* Step 2 : Create InputSteam object with source */
			FileInputStream fileIn = new FileInputStream(new File("employee.ser"));
			/* Step 3 : Create ObjectInputStream with using InputSteam created in step 2.*/
			ObjectInputStream in = new ObjectInputStream(fileIn);
			/* Step 4 : Call readObject method on ObjectInputStream object.*/
			emp = (Employee) in.readObject();
			in.close();
			fileIn.close();
		} catch (IOException ioex) {
			ioex.printStackTrace();
			return;
		} catch (ClassNotFoundException c) {
			System.out.println("Employee class not found");
			c.printStackTrace();
			return;
		}

		System.out.println("Deserialized Employee...");
		System.out.println("Emp id: " + emp.getEmpId());
		System.out.println("Name: " + emp.getEmpName());
		System.out.println("Basic: " + emp.getBasic());
	}
}

Do not want to serialize a field ?

  • If you don’t want to serialize any field, then make it transient.

Ex:

If you do not want to serialize password filed of User class then you mark the password field as trancient as below:

User.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package concepts.serialization.transientfield;

import java.io.Serializable;

public class User implements Serializable{
	private String userName;
	transient private String password;

	public User(String userName, String password) {
		super();
		this.userName = userName;
		this.password = password;
	}	
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}	
}

Note: Values of transient fields after Deserializing would null;

Specific scenarios

  • You can’t serialize static variables.

Caching Objects in the Stream

  • By default ObjectOutputStream will maintain a reference to an object written to it. That means that once state of an object written then further writes to same ObjectOutputStream will not be saved.

Ex:

	ObjectOutputStream out = new ObjectOutputStream(...);
	MyObjectobj = new MyObject(); // must be Serializable
	obj.setState(100);
	out.writeObject(obj); // saves object with state = 100
	obj.setState(200);
	out.writeObject(obj); // does not save new object state

There are two ways to control this :

  • Close the stream after each write call.
  • We can call ObjectOutputStream.reset() method before write object, which would tell the stream to release the cache of references it is holding.

Customizing Serialization

When an object gets serialized ObjectOutputStream looks for below methods(called magic methods) which developer can provide to customize the serialization. Below are the serialization magic methods in the order they called in pipeline.

  • writeReplace : This method allows the developer to provide a replacement object that will be serialized instead of original object. Signature:
	private Object writeReplace() throws ObjectStreamException;
  • wirteObject : This method allow to take control over what will be serialized. In most cases, you will just call ObjectOutputStream.defaultWriteObject() method to use default serialization process, then add some data of your choice.

    Signature:

	private void writeObject (ObjectOutputStream out) throws IOException;

Customizing Deserialization

When object gets deserialized ObjectInputSteam looks for below methods (called magic methods) which developer can provide to customize deserialization. Below are the deserialization magic methods in the order they called in pipeline.

  • readObject : This method allows to take control over how do we deserialize. In most cases, you will just call ObjectInputStream.defaultReadObject() to use default deserialization process, then read custom data.

    Signature:

	private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
  • readResolve: It may be used to replace the deserialized object by another one of your choice.

    Signature:

	private Object readResolve() throws ObjectStreamException
  • validateObject :If serialization object implements ObjectInputValidation, you may register it as a stream validator. This is useful to verify the stream has valid data that makes sense before handling it back to application.

    Signature:

	public void validateObject() throws InvalidObjectException

Custom Serialization/Deserialization Example

Assume the same example as User object where we want to serialize the password but in encripted format and decript it during deserialization. User.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package concepts.serialization.customserialization;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class User implements Serializable{
	private String userName;
	transient private String password;

	public User(String userName, String password) {
		super();
		this.userName = userName;
		this.password = password;
	}	
	
	private void writeObject(ObjectOutputStream oos) throws IOException {
		oos.defaultWriteObject();
		oos.writeObject(encript(password));
		System.out.println("Write");
	}
	
	private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
		ois.defaultReadObject();
		password = decript((String) ois.readObject());
		System.out.println("Read");
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	
	public String encript(String str) {
		StringBuffer buf = new StringBuffer(str);
		StringBuffer temp = new StringBuffer("");
		for(int i=0; i < buf.length(); i++) {
			temp.append((char)(buf.charAt(i) + 1));
		}
		return temp.toString();
	}
	
	public String decript(String str) {
		StringBuffer buf = new StringBuffer(str);
		StringBuffer temp = new StringBuffer("");
		for(int i=0; i < buf.length(); i++) {
			temp.append((char)(buf.charAt(i) - 1));
		}
		return temp.toString();		
	}
}
	

CustomizedSerialization.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package concepts.serialization.customserialization;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class CustomizedSerialization {

	public static void main(String[] args) {
		try
		{
			User user = new User("Deepak", "password");
			byte[] bytes = serialize(user);
			User dUser = (User)deSerialize(bytes);
			
			System.out.println("User Name :" + dUser.getUserName());
			System.out.println("Password :" + dUser.getPassword());
			
		} catch(IOException ioe) {
			
		}
		catch(ClassNotFoundException cnf) {
			
		}
	}

	public static byte[] serialize(Object obj) throws IOException  {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream(baos);
		oos.writeObject(obj);
		oos.flush();
		oos.close();
		return baos.toByteArray();
	}
	
	public static Object deSerialize(byte[] bytes) throws IOException, ClassNotFoundException {
		ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        ObjectInputStream ois = new ObjectInputStream(bais);
        Object o = ois.readObject();
        ois.close();
        return o;
	}
}	

Points to remember in customization of Serialization/Deserialization

Developer can enhance the default serialization by providing below above private methods (excluding validateObject) in their class:

  • As these method are private cannot be overloaded or overridden.
  • By writing these method we are not overriding/overloading existing methods. The virtual machine will automatically check to see if either method is declared and will call using reflections API.
  • This kind of customization is useful for defining pre and post process of serialization and deserialization and actual serialization and deserialization is done by default protocol.
  • This method will not give full control over serialization/deserialization as default serialization/deserialization is done.

Inheritance

  • If super class is Serializable then its subclasses are automatically Serializable.
  • If super class is not Serializable then all values of the instance variables inherited from super class will be initialized by calling constructor of Non-Serializable Super class during deserialization process.
  • If you don’t want subclass to serializable then you need to implement writeObject() and readObject() method and need to throw NotSerializableException from this methods.

Version control

  • serialVersionUID is used during the deserialization process to verify that the sender and receiver of serialized object have loaded class for that object which is compatible with respect to serialization.
  • Defining serialVersionUID field in serializable class is not mandatory.
  • If serializable class has serialVersionUID field then it should long type and it should static and final.
  • If there is no serialVersionUID field defined explicitly, then serialization runtime will calculate default value for that class. Which can vary based on compiler. Hence it is advisable to define serialVersionUID.
  • It is advised to use private modifier for serialVersionUID.
  • If there is difference between serialVersionUID of loaded receiver class and corresponding sender class then InvalidClassException will be thrown.