We have discussed the default serialization of objects. To disable serialization of fields we used the transient keyword. But sometimes this is not enough. You may need some kind of validation fields when deserializing a project. To achieve it, you should prefer to use custom serialization. There can be some design constraints as well: the class is expected to be changed in future releases which could break the deserialization of previously serialized objects.

How to customize serialization?

Java gives us two methods that we can use to customize the serialization process. These methods are:

  • writeObject()
  • readObject()

Now, this could be a bit strange to you. This is just a built-in feature of Java serialization. None of these methods are inherited, overridden or overloaded. You simply have to implement these two methods in your serializing class with your custom logic for serialization. This is how it should look:

public class ClassName implements Serializable {
    
    // transient and non-transient fields

    private void writeObject(ObjectOutputStream oos) throws Exception {
       // write the custom serialization code here
    }

    private void readObject(ObjectInputStream ois) throws Exception {
       // write the custom deserialization code here
    }
}

When you call the oos.writeObject() method, JVM first checks whether you have implemented the writeObject() method in your serializing class. If so, JVM executes the code inside that method instead of doing default serialization. Similarly, JVM will call the readObject() method in the serializing class when you call the ois.readObject() method.

Initialize transient variables

As you know, the oos.writeObject() doesn’t serialize transient fields. In the following example, we are solving this problem by initializing the password with an empty string when deserializing the object by means of the methods described above.

public class User implements Serializable {
    String userName = "admin";
    transient String password = "password";
  
    private void readObject(ObjectInputStream ois) throws Exception {
        ois.defaultReadObject();
        password = new String(" ");
    } 
}

Here, we don’t have to implement the writeObject() method as we don’t want to add anything to the serialization process. We only have to implement the readObject() method. Our first line is ois.defaultReadObject() which will perform the default deserialization. It means that after the ois.defaultReadObject() method you have the normal values for non-transient fields and null values for transient fields. Next, we instantiate the password with password = new String(" "). The code in the User class will remain the same.

More examples of custom serialization

There are many other reasons to use custom serialization. For example:

  • When you want to encrypt important fields of a class
  • When you want to use a more compressed serialization.

Let’s see how to encrypt the fields of a class. We have two functions, encrypt and decrypt, that we can use for encryption. Their implementations are not of importance here, let’s just assume that they are available to us.

This code uses both writeObject() and readObject() methods:

public class User implements Serializable {
    String userName = "admin";
    transient String password = "password";

    private void writeObject(ObjectOutputStream oos) throws Exception {
        oos.defaultWriteObject();
        String encryptPassword = encrypt(password);
        oos.writeObject(encryptPassword);
    }

    private void readObject(ObjectInputStream ois) throws Exception {
        ois.defaultReadObject();
        password = decrypt((String) ois.readObject());
    }
}

First, oos.defaultWriteObject() in writeObject() method will perform the default serialization on non-transient fields. Then we will encrypt the password using encrypt() method. Next, we will serialize the encrypted password. Likewise, ois.defaultReadObject() method will deserialize non-transient fields. Then using the readObject() method, you can retrieve the encryptPassword field. Finally, use the decrypt() method to decrypt the variable.

Processing standard data types

Both ObjectOutputStream and ObjectInputStream provide useful methods to serialize standard types Stringintboolean, and others. The methods are ois.readUTF()oos.writeUTF()ois.readInt()oos.writeInt()ois.readBoolean()oos.writeBoolean(), and so on.

In some cases, several methods may be applied. For strings, you can use either read/write UTF or read/write object methods. There is only one restriction: use the same consistent way of serializing and deserializing to avoid problems.

Conclusion

In this topic, we’ve covered a couple of things. First, we’ve explained why we need to prevent some fields from being serialized and how the transient keyword can help you do that. Next, we’ve discussed custom serialization and how to implement it with just two methods. It’s obvious that Java provides us with an easy way to customize the serialization, but what matters is how we can write an effective code for custom serialization.

Leave a Reply

Your email address will not be published.