Java Custom Serialization using readObject() and writeObject()

We may need custom serialization in java in many cases. For example, we have legacy java classes that we are not willing to modify for any reason. There can be some design constraints as well. Or even simply, the class is expected to be changed in future releases which could break the deserialization of previously serialized objects.

1. Custom Serialization

In most cases, when we will customize java serialization, you will write the fields one by one – in a sequence. It is the most common way to override default java serialization process.

Let’s say, we have one User object and we want to customize its serialization process.

public class User implements Serializable {

	private static final long serialVersionUID = 7829136421241571165L;

	private String firstName;
	private String lastName;
	private int accountNumber;
	private Date dateOpened;

	public User(String firstName, String lastName, int accountNumber, Date dateOpened) {
		super();
		this.firstName = firstName;
		this.lastName = lastName;
		this.accountNumber = accountNumber;
		this.dateOpened = dateOpened;
	}

	public User() {
		super();
	}

	public final String getFirstName() {
		return firstName;
	}

	public final String getLastName() {
		return lastName;
	}

	public final int getAccountNumber() {
		return accountNumber;
	}

	public final Date getDateOpened() {
		return new Date(dateOpened.getTime());
	}

	public final void setFirstName(String aNewFirstName) {
		firstName = aNewFirstName;
	}

	public final void setLastName(String aNewLastName) {
		lastName = aNewLastName;
	}

	public final void setAccountNumber(int aNewAccountNumber) {
		accountNumber = aNewAccountNumber;
	}

	public final void setDateOpened(Date aNewDate) {
		Date newDate = new Date(aNewDate.getTime());
		dateOpened = newDate;
	}
}

2. Implementing readObject() and writeObject()

To customize serialization and deserialization, define readObject() and writeObject() methods in this class.

  • Inside writeObject() method, write class attributes using writeXXX methods provided by ObjectOutputStream.
  • Inside readObject() method, read class attributes using readXXX methods provided by ObjectInputStream.
  • Please note that the sequence of class attributes in read and write methods MUST BE the same.
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Date;

public class User implements Serializable {

	//...

	private void readObject(ObjectInputStream aInputStream) throws ClassNotFoundException, IOException
	{
		firstName = aInputStream.readUTF();
		lastName = aInputStream.readUTF();
		accountNumber = aInputStream.readInt();
		dateOpened = new Date(aInputStream.readLong());
	}

	private void writeObject(ObjectOutputStream aOutputStream) throws IOException
	{
		aOutputStream.writeUTF(firstName);
		aOutputStream.writeUTF(lastName);
		aOutputStream.writeInt(accountNumber);
		aOutputStream.writeLong(dateOpened.getTime());
	}
}

Now let’s test the code.

3. Demo

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Calendar;
import java.util.Date;

public class TestCustomSerialization
{
	public static void main(String[] args)
	{
		// Create new User object
		User myDetails = new User("Lokesh", "Gupta", 102825, new Date(Calendar.getInstance().getTimeInMillis()));

		// Serialization code
		try
		{
			FileOutputStream fileOut = new FileOutputStream("User.ser");
			ObjectOutputStream out = new ObjectOutputStream(fileOut);
			out.writeObject(myDetails);
			out.close();
			fileOut.close();
		}
		catch (IOException i)
		{
			i.printStackTrace();
		}

		// De-serialization code
		User deserializedUser = null;
		try
		{
			FileInputStream fileIn = new FileInputStream("User.ser");
			ObjectInputStream in = new ObjectInputStream(fileIn);
			deserializedUser = (User) in.readObject();
			in.close();
			fileIn.close();

			// verify the object state
			System.out.println(deserializedUser.getFirstName());
			System.out.println(deserializedUser.getLastName());
			System.out.println(deserializedUser.getAccountNumber());
			System.out.println(deserializedUser.getDateOpened());
		}
		catch (IOException ioe)
		{
			ioe.printStackTrace();
		}
		catch (ClassNotFoundException cnfe)
		{
			cnfe.printStackTrace();
		}
	}
}

The program output:

Lokesh
Gupta
102825
Wed May 24 13:05:25 IST 2017

4. Post-Serialization Data Validation

Sometimes we may have requirements where you only want to perform any specific validation or run some business rules on deserialized objects – without affecting the default java serialization mechanism. This is also possible when we decide to use readObject() and writeObject() methods.

In this usecase, you can use defaultReadObject() and defaultWriteObject() inside readObject() and writeObject() methods – to enable default serialization and deserialization. And you can then plugin your custom validation or business rules inside read/write methods.

This way your validation methods will be automatically called by JVM, immediately after the default serialization and deserialization process happens.

public class User implements Serializable {

	//class attributes, constructors, setters and getters as shown above

	/**
	 * Always treat de-serialization as a full-blown constructor, by validating the final state of the de-serialized object.
	 */
	private void readObject(ObjectInputStream aInputStream) throws ClassNotFoundException, IOException
	{
		// perform the default de-serialization first
		aInputStream.defaultReadObject();

		// make defensive copy of the mutable Date field
		dateOpened = new Date(dateOpened.getTime());

		// ensure that object state has not been corrupted or tampered with malicious code
		//validateUserInfo();
	}

	/**
	 * This is the default implementation of writeObject. Customize as necessary.
	 */
	private void writeObject(ObjectOutputStream aOutputStream) throws IOException {

		//ensure that object is in desired state. Possibly run any business rules if applicable.
		//checkUserInfo();

		// perform the default serialization for all non-transient, non-static fields
		aOutputStream.defaultWriteObject();
	}
}

Test the code again and you will see this output:

Lokesh
Gupta
102825
Wed May 24 13:10:18 IST 2017

5. Conclusion

As we saw that custom serialization is straightforward in java and it involves very simple design i.e. implementing readObject() and writeObject() methods; and add any additional logic to support the application business logic.

Though default serialization/deserialization will be enough in most of cases; still when required you shall use custom serialization in java applications.

Drop me your questions in the comments section.

Happy Learning !!

Comments

Subscribe
Notify of
guest
12 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode