Java Serialization

What is Java Serialization?

Serialization is the process used to convert an object state into stream of bytes so that it can be written into a file, transported through a network or stored into database. De-serialization is just a vice versa.

Why do we need Serialization in Java?

We need Serialization for the following reasons:

  • Save object state into a file
    • State of an object is available in JVM until it’s scope, If we need to hold state of an object even after it’s scope then we could serialize object state into a file on the local disk and de-serialize when ever we need.
  • Transfer object state
    • If we would like to transfer the state of an object across multiple JVM’s, we could serialize and transfer from one JVM to another JVM in distributed applications
  • Save object state into database
    • If we would like to save state of an object permanently, then we could serialize and save into database so that it can be retrieved later
  • Clone of an object
    • If we want an exact copy (deep copy) of an object, then we can serialize the object to a stream of bytes, and then de-serializing it
Java serialization – deserialization process

How to make a java class serializable?

A class must implement either java.io.Serializable or java.io.Externalizable interface to be eligible for serialization. java.io.Serializable is a marker interface that means it doesn’t contains any methods or fields.

If we try to serialize a non-serializable object, then we hit with an exception called java.io.NotSerializableException.

What is the extension of serialized file ?

Even other extensions are allowed, The .ser extension is preferred to be used for files representing serialized objects

transient keyword

transient modifier is applicable only for variables. transient variables not serialized during serialization process. During de-serialization process transient variables will be initialized with default value. (e.g: for objects it is null, for int it is 0).

static vs transient

A static variable is not part of an object state, hence static variables not serialized during serialization process. Due to this there is no need of declaring static variable as transient. During de-serialization process static variables will be assigned with current value defined in the loaded class. 

Java Serialization with Aggregation (has-a Relationship)

A serializable class should have all serializable references. If we try to serialize a serializable object which has non-serializable references, then we will hit with an exception called java.io.NotSerializableException.

Java Serialization with array or collection

All primitive arrays and collections(list/set/map … etc.) will be serialized and de-serialized successfully. All user-defined object arrays and collections will get serialized and de-serialized successfully if user-defined object is serializable Otherwise we will hit with an exception called java.io.NotSerializableException.

How to do Serialization and Deserialization ?

First we need to define a domain class which implements Serializable marker interface with it’s attributes. Here in our example we are defining Employee class with serialVersionUID, and two instance variables, final variable, static variable, transient variable, constructors, setter & getters, toString() method as shown below.

In the above example, comments explain each variable and their behavior clearly.

To serialize and de-serialize objects, Java API has given ObjectInputStream and ObjectOutputStream high level classes that extend java.io.InputStream and java.io.OutputStream respectively.  ObjectOutputStream can write primitive types and graphs of objects to an OutputStream as a stream of bytes. These streams can subsequently read back using ObjectInputStream.

The most important method in ObjectOutputStream is:

Which takes a serializable object and converts it into a stream of bytes. Similarly, the most important method in ObjectInputStream is:

Which can read a stream of bytes and convert it back into a Java object. This can then be cast back to the original object.

The following example code we used to serialize and de-serialize Employee domain object.

Output : When we run the above program, it will create a file Employee.ser” in the project directory. The same will be loaded in the de-serialization process.

————Contructor————
Before serialization => Employee [employeeId=1001, name=Sekhar Reddy, salary=99999.0, designation=Architect, department=IT]
After deserialization => Employee [employeeId=1001, name=Sekhar Reddy, salary=0.0, designation=Architect, department=IT]

As per the above output,

  • We can see that instance variables employeeId, name are successfully serialized and de-serialized.
  • The transient variable salary was not serialized that’s why in de-serialization transient variable salary was assigned with default value 0.0.
  • The final variable department was are successfully serialized and de-serialized.
  • The static variable designation also seems like successfully serialized and de-serialized. But it is not the truth. Let’s run the following deserialization logic shown below.

Output :

After deserialization => Employee [employeeId=1001, name=Sekhar Reddy, salary=0.0, designation=null, department=IT]

Surprise !!! As per the above output, static variable not serialized and de-serialized. But in the previous example, static variable value was assigned with current value defined in the loaded class during de-serialization. So static variables are not serialized and de-serialized.

final vs transient

Only final variable are will get serialized. But final transient variables will not get serialized, But while de-serializing, final-transient variable explicitly initialized, the value is re-affected. We will understand final transient behavior with the following example.

Output :

————–Constructor—————
Before serialization => FinalVsTransientDemo [initializedVariable=Vidvaan, declarationVariable=Vidvaan, dynamicExpressionVariable=SomaSekharReddy, constantExpressionVariable=VidvaanVidvaan1, intConstantExpressionVariable=6, intDynamicExpressionVariable=123, arrayVariable=[1, 2, 3], listVariable=[Sekhar, Reddy]]
After deserialization => FinalVsTransientDemo [initializedVariable=Vidvaan, declarationVariable=null, dynamicExpressionVariable=null, constantExpressionVariable=VidvaanVidvaan1, intConstantExpressionVariable=6, intDynamicExpressionVariable=0, arrayVariable=null, listVariable=null]

In the above example, During de-serialization, final-transient declared variable is set to default values. final-transient initialized variables re-affected, final-transient constant expressions re-affected, final-transient dynamic expressions set to default values.

We can assign values to final fields in constructors only and de-serialization process do not invoke any constructor but still it can assign values to final fields.

Java Serialization with Inheritance(is-a Relationship)

If parent class is serializable class, then by default child class is serializable. So when we serialize child class object, then both parent and child class instance variables will get serialized. In de-serialization also both parent and child class instance variables will get de-serialized successfully. Let’s see an example.

Output :

————-Parent Constructor————-
————-Child Constructor————-
————-Before serialization————-
parentVariable = Vidvaan-Parent
childVariable = Vidvaan-Child
————-After deserialization————-
parentVariable = Vidvaan-Parent
childVariable = Vidvaan-Child

If parent class is not serializable but only child class implements serializable, Then when we serialize child class object, Only child class instance variables will get serialized but parent class instance variables will not get serialized. In de-serialization only child class instance variables will get de-serialized, but parent instance variables assigned with default values. Let’s see an example.

Output :

————-SuperClass Parameterized Constructor————-
————-SubClass Constructor————-
————-Before serialization————-
parentVariable = Vidvaan-Parent
childVariable = Vidvaan-Child
————-SuperClass No Argument Constructor————-
————-After deserialization————-
parentVariable = null
childVariable = Vidvaan-Child

As per the above output, In the de-serialization process, Interesting thing is super class default contractor is called, If we don’t define default constructor in super class, then we will hit with an exception.

What is the serialVersionUID?

SerialVersionUID is an id, which acts as a version number for each serializable class. It is used to ensure that during de-serialization the same class is loaded, which was used during serialization.

If we don’t give serialVersionUID in serializable class, JVM generates one for our class, based on class structure(attributes, types, access modifiers). In General when we change the class structure, then JVM generates different serialVersionUID. There is a chance that, different JVM’s(different vendors, different versions, different platform) generates different serialVersionUID even for the same class.

Suppose, when we are sending object state from one application to another application which are running on different JVM’s(different vendor JVM’s or different platform JVM’s or different version JVM’s) then serialVersionUID of both sender and receiver classes should be same. Otherwise de-serialization will be failed.

In serialization, when we serialize object state, along with object state serialVersionUID also get serialized, which will be used in the de-serialization process.

In de-serialization, JVM reads serialVersionUID from serialized file and compares with loaded class serialVersionUID. If they don’t match then we will get an exception called InvalidClassException.

So it is always recommended that each Serializable class should explicitly declare its serialVersionUID.

Let’s see an example to demonstrate serialVersionUID.

Person.java : Here we are not declaring serialVersionUID.

SerializeSerialVersionUIDExample.java

Output :

Before serialization => Person [firstName=Sekhar Reddy, lastName=Yerragudi]

Now in Person.java, uncomment linenumber:15 to add new attribute called ‘newVariable’. Then run the following de-serialization example.

DeserializeSerialVersionUIDExample.java

Output :

Exception in thread “main” java.io.InvalidClassException: com.vidvaan.corejava.serilaization06.SerialVersionUID.Person; local class incompatible: stream classdesc serialVersionUID = 8304851845376733956, local class serialVersionUID = 1072717160948856604

As per the above output, we can understand that, when we added new variable to the Person class, JVM has created new serialVersionUID. In our case we may have the requirement that, for the new variable JVM would assign default value in the de-serialization process. But it will fail if we don’t give serialVersionUID. Now re-run above examples in the same sequence by giving serialVersionUID in Person class. Then it will run de-serialization successfully even after adding new attribute. Hence it is always recommended to give serialVersionUID explicitly for all serializable classes.

Customizing Serialization and Deserialization?

Java has given the default way in which objects can be serialized and de-serialized. But we can customize the serialization process in our serializable class by override the default behavior. Custom serialization particularly useful when we are trying to serialize an object that has some non-serializable attributes. This can be done by providing two methods, writeObject and readObject, inside the class that we want to serialize.

These methods always should be private, because these are not meant for any one to call explicitly. Even these methods are private, Still they can be called by JVM in the serialization and de-serialization process. Let’s see an example to customize serialization and de-serialization as shown below.

Address.java

Student.java

CustomSerializationAndDeserializationExample.java

Output :

————-Constructor————-
Before s erialization => Student [studentNumber=1001, studentName=Sekhar Reddy, address=Address [houseNumber=11199, street=Seward Plaza, city=Omaha, zipCode=68154]]
————-Custom serialization logic————-
————-Custom deserialization logic————-
After deserialization => Student [studentNumber=1001, studentName=Sekhar Reddy, address=Address [houseNumber=11199, street=Seward Plaza, city=Omaha, zipCode=null]]

In the above example, we have serialized Address (non-serializable) with custom serialization. Note that we should mark the non-serializable attributes as transient to avoid the NotSerializableException. Note that, in the de-serialization we need to extract serialized variables in the same exact order in which they were serialized.

How to prevent serialization and de-serialization ?

We can prevent serialization and de-serialization of class which has got the serialization capability. Some times our class may got serialization capability from parent class, But we don’t want to allow our class to be serialized and de-serialized. To do so in our class we need to add following two private methods.

The above methods will be called while serialization and de-serialization. In these methods we are throwing exception NotSerializableException. Let’s see an example below.

Output :

Before serialization => Animal [name=Tiger]
Exception in thread “main” java.io.NotSerializableException: Animal object not allowed for serialization

Why java.lang.Object not implementing Serializable?

Because, what if we do NOT want to make certain fields as Serializable(may be they are holding some credentials information) and by mistake we missed to add transient to those fields? Then it will be a potential security hole. 

We may not want to serialize all the objects. Even serializing some objects like Thread and de-serializing them in another JVM does not make any sense.

Serialization using Externalizable interface

The usage of Java Externalizable interface is clearly explained here.

Scroll to Top