Java custom serialization example

03 February 2013
By Gonçalo Marques
In this tutorial you will learn how to override the default Java serialization mechanism and also how to provide a custom serialization implementation.

Introduction

By implementing the Serializable interface we are defining our class to be serializable by the JVM (we have already seen basic serialization before at Java Serializable example). This time we will cover the custom serialization: how it should be implemented and how it is called by the JVM.

Environment:

  1. Ubuntu 12.04
  2. JDK 1.7.0.09

Serializable - writeObject and readObject

When we implement the Serializable interface we are defining our class to be serializable. We have already seen that this is enough to support the default Java serialization mechanism. When we need to implement custom serialization we also have to define writeObject and/or readObject methods. Why did I mentioned define these methods and not override? Because we are actually not overriding anything: The JVM checks and calls these methods by the means of reflection.

Example one

Let's define a simple class to use in this example:

TestClass.java

package com.byteslounge.serialization;

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

public class TestClass implements Serializable {

  private static final long serialVersionUID = -2518143671167959230L;
  
  private String propertyOne;
  private String propertyTwo;
  
  public TestClass(String propertyOne, String propertyTwo) {
    this.propertyOne = propertyOne;
    this.propertyTwo = propertyTwo;
    validate();
  }
  
  private void writeObject(ObjectOutputStream o)
    throws IOException {  
    
    o.writeObject(propertyOne);  
    o.writeObject(propertyTwo);
  }
  
  private void readObject(ObjectInputStream o)
    throws IOException, ClassNotFoundException {  
    
    propertyOne = (String) o.readObject();  
    propertyTwo = (String) o.readObject();
    validate();
  }

  private void validate(){
    if(propertyOne == null || 
      propertyOne.length() == 0 || 
      propertyTwo == null || 
      propertyTwo.length() == 0){
      
      throw new IllegalArgumentException();
    }
  }
  
  public String getPropertyOne() {
    return propertyOne;
  }
  
  public String getPropertyTwo() {
    return propertyTwo;
  }
  
}

We have defined two properties: propertyOne and propertyTwo. We have also defined writeObject and readObject methods, and once again note that we are not overriding anything as these methods will be called by the JVM using reflection (we actually defined these methods as private). Finally we defined the validate method that will be used to validate if the object is correctly initialized in both construction and deserialization events (in our example we are just checking if both properties are not null or empty).

Testing Example one

Let's define a simple class to test our example:

Main.java

package com.byteslounge.serialization;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Main {

  public static void main(String[] args) 
    throws Exception {
    
    TestClass testWrite = new TestClass("valueOne", "valueTwo");
    FileOutputStream fos = new FileOutputStream("testfile");
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    oos.writeObject(testWrite);
    oos.flush();
    oos.close();
      
    TestClass testRead;
    FileInputStream fis = new FileInputStream("testfile");
    ObjectInputStream ois = new ObjectInputStream(fis);
    testRead = (TestClass) ois.readObject();
    ois.close();
      
    System.out.println("--Serialized object--");
    System.out.println("propertyOne: " + testWrite.getPropertyOne());
    System.out.println("propertyTwo: " + testWrite.getPropertyTwo());
    System.out.println("");
    System.out.println("--Read object--");
    System.out.println("propertyOne: " + testRead.getPropertyOne());
    System.out.println("propertyTwo: " + testRead.getPropertyTwo());

  }

}

When we run this test the following output will be generated:

--Serialized object--
propertyOne: valueOne
propertyTwo: valueTwo

--Read object--
propertyOne: valueOne
propertyTwo: valueTwo

Example two

Let's define another class:

AnotherClass.java
package com.byteslounge.serialization;

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

public class AnotherClass implements Serializable {

  private static final long serialVersionUID = -5606842333916087978L;
  
  private String propertyOne;
  private transient String propertyTwo;
  
  public AnotherClass(String propertyOne, String propertyTwo) {
    this.propertyOne = propertyOne;
    this.propertyTwo = propertyTwo;
  }
  
  private void writeObject(ObjectOutputStream o)
    throws IOException {  
    
    o.defaultWriteObject();  
    o.writeObject(propertyTwo);
  }
  
  private void readObject(ObjectInputStream o)
    throws IOException, ClassNotFoundException {
    
    o.defaultReadObject();
    propertyTwo = (String) o.readObject();
  }
  
  public String getPropertyOne() {
    return propertyOne;
  }
  
  public String getPropertyTwo() {
    return propertyTwo;
  }
  
}

This time we defined a class with a transient property. We have already seen that transient properties are not serialized by default (Java transient modifier example). We defined writeObject and readObject once again but this time we call the default serialization mechanism inside them. During serialization we call the default serialization - defaultWriteObject() - and then we call writeObject(propertyTwo). This way we write the transient property that would not be serialized by default. The same is valid for deserialization with defaultReadObject() and o.readObject().

Testing Example two

Let's define a simple class to test our example:

Main.java

package com.byteslounge.serialization;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Main {

  public static void main(String[] args) 
    throws Exception {
    
    AnotherClass testWrite = new AnotherClass("valueOne", "valueTwo");
    FileOutputStream fos = new FileOutputStream("testfile");
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    oos.writeObject(testWrite);
    oos.flush();
    oos.close();
      
    AnotherClass testRead;
    FileInputStream fis = new FileInputStream("testfile");
    ObjectInputStream ois = new ObjectInputStream(fis);
    testRead = (AnotherClass)ois.readObject();
    ois.close();
      
    System.out.println("--Serialized object--");
    System.out.println("propertyOne: " + testWrite.getPropertyOne());
    System.out.println("propertyTwo: " + testWrite.getPropertyTwo());
    System.out.println("");
    System.out.println("--Read object--");
    System.out.println("propertyOne: " + testRead.getPropertyOne());
    System.out.println("propertyTwo: " + testRead.getPropertyTwo());

  }

}

When we run this test the following output will be generated:

--Serialized object--
propertyOne: valueOne
propertyTwo: valueTwo

--Read object--
propertyOne: valueOne
propertyTwo: valueTwo

Conclusion

We have seen two examples of custom serialization but they were just used as illustrative scenarios. The real usage of a custom serialization mechanism it's really bound to specific use cases, like using a custom format or infrastructure to store the serialized data or even refine the default serialization under some specific scenario.

The example source code is available for download at the end of this page.

Download source code from this article

Related Articles

Comments

About the author
Gonçalo Marques is a Software Engineer with several years of experience in software development and architecture definition. During this period his main focus was delivering software solutions in banking, telecommunications and governmental areas. He created the Bytes Lounge website with one ultimate goal: share his knowledge with the software development community. His main area of expertise is Java and open source.

GitHub profile: https://github.com/gonmarques

He is also the author of the WiFi File Browser Android application: