What is Serialization
Serialization is the process of converting an object's state (including its references) to a sequence of bytes, as well as the process of rebuilding those bytes into a live object at some future time. Serialization is used when you want to persist the object. It is also used by Java RMI to pass objects between JVMs, either as arguments in a method invocation from a client to a server or as return values from a method invocation, or as exceptions thrown by remote methods. In general, serialization is used when we want the object to exist beyond the lifetime of the JVM.
java.io.Serializable
is a marker interface (has no body). It is just used to "mark" Java classes as serializable.
The serialization runtime associates with each serializable class a version number, called a serialVersionUID
, which is used during de-serialization to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to serialization. If the receiver has loaded a class for the object that has a different serialVersionUID
than that of the corresponding sender's class, then deserialization will result in an InvalidClassException
. A serializable class can declare its own serialVersionUID
explicitly by declaring a field named serialVersionUID
that must be static, final,
and of type long
:
ANY-ACCESS-MODIFIER static final long serialVersionUID = 1L;
How to make a class eligible for serialization
To persist an object the respective class must implement the java.io.Serializable
interface.
import java.io.Serializable;
public class SerialClass implements Serializable {
private static final long serialVersionUID = 1L;
private Date currentTime;
public SerialClass() {
currentTime = Calendar.getInstance().getTime();
}
public Date getCurrentTime() {
return currentTime;
}
}
How to write an object into a file
Now we need to write this object to a file system. We use java.io.ObjectOutputStream
for this purpose.
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
public class PersistSerialClass {
public static void main(String [] args) {
String filename = "time.ser";
SerialClass time = new SerialClass(); //We will write this object to file system.
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(filename));
out.writeObject(time); //Write byte stream to file system.
out.close();
} catch(IOException ex){
ex.printStackTrace();
}
}
}
How to recreate an object from its serialized state
The stored object can be read from file system at later time using java.io.ObjectInputStream
as shown below:
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.IOException;
import java.io.java.lang.ClassNotFoundException;
public class ReadSerialClass {
public static void main(String [] args) {
String filename = "time.ser";
SerialClass time = null;
try {
ObjectInputStream in = new ObjectInputStream(new FileInputStream(filename));
time = (SerialClass)in.readObject();
in.close();
} catch(IOException ex){
ex.printStackTrace();
} catch(ClassNotFoundException cnfe){
cnfe.printStackTrace();
}
// print out restored time
System.out.println("Restored time: " + time.getTime());
}
}
The serialized class is in binary form. The deserialization can be problematic if the class definition changes: see the Versioning of Serialized Objects chapter of the Java Serialization Specification for details.
Serializing an object serializes the entire object graph of which it is the root, and operates correctly in the presence of cyclic graphs. A reset()
method is provided to force the ObjectOutputStream
to forget about objects that have already been serialized.
Serialization with Gson is easy and will output correct JSON.
public class Employe {
private String firstName;
private String lastName;
private int age;
private BigDecimal salary;
private List<String> skills;
//getters and setters
}
(Serialization)
//Skills
List<String> skills = new LinkedList<String>();
skills.add("leadership");
skills.add("Java Experience");
//Employe
Employe obj = new Employe();
obj.setFirstName("Christian");
obj.setLastName("Lusardi");
obj.setAge(25);
obj.setSalary(new BigDecimal("10000"));
obj.setSkills(skills);
//Serialization process
Gson gson = new Gson();
String json = gson.toJson(obj); //{"firstName":"Christian","lastName":"Lusardi","age":25,"salary":10000,"skills":["leadership","Java Experience"]}
Note that you can not serialize objects with circular references since that will result in infinite recursion.
(Deserialization)
//it's very simple...
//Assuming that json is the previous String object....
Employe obj2 = gson.fromJson(json, Employe.class); // obj2 is just like obj
Following is an implementation that demonstrates how an object can be serialized into its corresponding JSON string.
class Test {
private int idx;
private String name;
public int getIdx() {
return idx;
}
public void setIdx(int idx) {
this.idx = idx;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Serialization:
Test test = new Test();
test.setIdx(1);
test.setName("abc");
ObjectMapper mapper = new ObjectMapper();
String jsonString;
try {
jsonString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(test);
System.out.println(jsonString);
} catch (JsonProcessingException ex) {
// Handle Exception
}
Output:
{
"idx" : 1,
"name" : "abc"
}
You can omit the Default Pretty Printer if you don't need it.
The dependency used here is as follows:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.6.3</version>
</dependency>
In this example we want to create a class that will generate and output to console, a random number between a range of two integers which are passed as arguments during the initialization.
public class SimpleRangeRandom implements Runnable {
private int min;
private int max;
private Thread thread;
public SimpleRangeRandom(int min, int max){
this.min = min;
this.max = max;
thread = new Thread(this);
thread.start();
}
@Override
private void WriteObject(ObjectOutputStreamout) throws IO Exception;
private void ReadObject(ObjectInputStream in) throws IOException, ClassNotFoundException;
public void run() {
while(true) {
Random rand = new Random();
System.out.println("Thread: " + thread.getId() + " Random:" + rand.nextInt(max - min));
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Now if we want to make this class Serializable there will be some problems. The Thread is one of the certain system-level classes that are not Serializable. So we need to declare the thread as transient. By doing this we will be able to serialize the objects of this class but we will still have an issue. As you can see in the constructor we set the min and the max values of our randomizer and after this we start the thread which is responsible for generating and printing the random value. Thus when restoring the persisted object by calling the readObject() the constructor will not run again as there is no creation of a new object. In that case we need to develop a Custom Serialization by providing two methods inside the class. Those methods are:
private void writeObject(ObjectOutputStream out) throws IOException;
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;
Thus by adding our implementation in the readObject() we can initiate and start our thread:
class RangeRandom implements Serializable, Runnable {
private int min;
private int max;
private transient Thread thread;
//transient should be any field that either cannot be serialized e.g Thread or any field you do not want serialized
public RangeRandom(int min, int max){
this.min = min;
this.max = max;
thread = new Thread(this);
thread.start();
}
@Override
public void run() {
while(true) {
Random rand = new Random();
System.out.println("Thread: " + thread.getId() + " Random:" + rand.nextInt(max - min));
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
thread = new Thread(this);
thread.start();
}
}
Here is the main for our example:
public class Main {
public static void main(String[] args) {
System.out.println("Hello");
RangeRandom rangeRandom = new RangeRandom(1,10);
FileOutputStream fos = null;
ObjectOutputStream out = null;
try
{
fos = new FileOutputStream("test");
out = new ObjectOutputStream(fos);
out.writeObject(rangeRandom);
out.close();
}
catch(IOException ex)
{
ex.printStackTrace();
}
RangeRandom rangeRandom2 = null;
FileInputStream fis = null;
ObjectInputStream in = null;
try
{
fis = new FileInputStream("test");
in = new ObjectInputStream(fis);
rangeRandom2 = (RangeRandom)in.readObject();
in.close();
}
catch(IOException ex)
{
ex.printStackTrace();
}
catch(ClassNotFoundException ex)
{
ex.printStackTrace();
}
}
}
If you run the main you will see that there are two threads running for each RangeRandom instance and that is because the Thread.start() method is now in both the constructor and the readObject().
When you implement java.io.Serializable
interface to make a class serializable, the compiler looks for a static final
field named serialVersionUID
of type long
. If the class doesn't have this field declared explicitly then the compiler will create one such field and assign it with a value which comes out of a implementation dependent computation of serialVersionUID
. This computation depends upon various aspects of the class and it follows the Object Serialization Specifications given by Sun. But, the value is not guaranteed to be the same across all compiler implementations.
This value is used for checking the compatibility of the classes with respect to serialization and this is done while de-serializing a saved object. The Serialization Runtime verifies that serialVersionUID
read from the de-serialized data and the serialVersionUID
declared in the class are exactly the same. If that is not the case, it throws an InvalidClassException
.
It's highly recommended that you explicitly declare and initialize the static, final field of type long and named 'serialVersionUID' in all your classes you want to make Serializable instead of relying on the default computation of the value for this field even if you are not gonna use versioning. 'serialVersionUID' computation is extremely sensitive and may vary from one compiler implementation to another and hence you may turn up getting the InvalidClassException
even for the same class just because you used different compiler implementations on the sender and the receiver ends of the serialization process.
public class Example implements Serializable {
static final long serialVersionUID = 1L /*or some other value*/;
//...
}
As long as serialVersionUID
is the same, Java Serialization can handle different versions of a class. Compatible and incompatible changes are;
We consume rest API as a JSON format and then unmarshal it to a POJO. Jackson’s org.codehaus.jackson.map.ObjectMapper “just works” out of the box and we really don’t do anything in most cases. But sometimes we need custom deserializer to fulfill our custom needs and this tutorial will guide you through the process of creating your own custom deserializer.
Let’s say we have following entities.
public class User {
private Long id;
private String name;
private String email;
//getter setter are omitted for clarity
}
And
public class Program {
private Long id;
private String name;
private User createdBy;
private String contents;
//getter setter are omitted for clarity
}
Let’s serialize/marshal an object first.
User user = new User();
user.setId(1L);
user.setEmail("[email protected]");
user.setName("Bazlur Rahman");
Program program = new Program();
program.setId(1L);
program.setName("Program @# 1");
program.setCreatedBy(user);
program.setContents("Some contents");
ObjectMapper objectMapper = new ObjectMapper();
final String json = objectMapper.writeValueAsString(program); System.out.println(json);
The above code will produce following JSON-
{
"id": 1,
"name": "Program @# 1",
"createdBy": {
"id": 1,
"name": "Bazlur Rahman",
"email": "[email protected]"
},
"contents": "Some contents"
}
Now can do the opposite very easily. If we have this JSON, we can unmarshal to a program object using ObjectMapper as following –
Now let’s say, this is not the real case, we are going to have a different JSON from an API which doesn’t match with our Program
class.
{
"id": 1,
"name": "Program @# 1",
"ownerId": 1
"contents": "Some contents"
}
Look at the JSON string, you can see, it has a different field that is owenerId.
Now if you want to serialize this JSON as we did earlier, you will have exceptions.
There are two ways to avoid exceptions and have this serialized –
Ignore the unknown fields
Ignore the onwerId
. Add the following annotation in the Program class
@JsonIgnoreProperties(ignoreUnknown = true)
public class Program {}
Write custom deserializer
But there are cases when you actually need this owerId
field. Let's say you want to relate it as an id of the User
class.
In such case, you need to write a custom deserializer-
As you can see, first you have to access the JsonNode
from the JonsParser
. And then you can easily extract information from a JsonNode
using the get()
method. and you have to make sure about the field name. It should be the exact name, spelling mistake will cause exceptions.
And finally, you have to register your ProgramDeserializer to the ObjectMapper
.
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Program.class, new ProgramDeserializer());
mapper.registerModule(module);
String newJsonString = "{\"id\":1,\"name\":\"Program @# 1\",\"ownerId\":1,\"contents\":\"Some contents\"}";
final Program program2 = mapper.readValue(newJsonString, Program.class);
Alternatively, you can use annotation to register the deserializer directly –
@JsonDeserialize(using = ProgramDeserializer.class)
public class Program {
}