HowToDoInJava

  • Python
  • Java
  • Spring Boot
  • Dark Mode
Home / Java / Serialization / Java custom serialization using readObject and writeObject

Java custom serialization using readObject and writeObject

You may need custom serialization in java in may cases. For example, you have legacy java classes which you 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.

Table of Contents

1. Custom Serialization
2. Default Serialization with Added Validation
3. Summary

1. Java Custom Serialization

In most of cases, when you will customize java serialization, you will be writing the fields one by one – in a sequence. Its most common way to override default java serialization process.

Let’s say, we have one User object and we want to customize it’s 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;
	}
}

1.1. readObject() and writeObject() methods

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 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 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();
	}

	//Setters and Getters

	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.

1.2. Test Custom Serialization

package com.howtodoinjava.io.example;

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();
		}
	}
}

//Output

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

2. Override default serialization to add validation

Sometimes you may have requirement where you only want to perform any specific validation, or run some business rules on deserialized object – without affecting default java serialization mechanism. This is also possible when you 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 you custom validation or business rules inside read/write methods.
This way you validation methods will be automatically called by JVM, immediately after 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 an you will see this output:

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

3. Summary

As we saw that custom serialization is very easy in java and it involve very simple design i.e. implement 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 the cases; still when required you shall use custom serialization in java applications.

Drop me your questions in comments section.

Happy Learning !!

Was this post helpful?

Let us know if you liked the post. That’s the only way we can improve.

Share this:

  • Twitter
  • Facebook
  • LinkedIn
  • Reddit

About Lokesh Gupta

A family guy with fun loving nature. Love computers, programming and solving everyday problems. Find me on Facebook and Twitter.

Feedback, Discussion and Comments

  1. Manish sinha

    July 13, 2020

    
    import java.io.*;
    
    public class Solution implements Serializable {
        public static class A  {
       String nameA = "A";
    //static String nameA="A";
    
            public A(){
    
            }
    
            public A(String nameA) {
                this.nameA += nameA;
            }
        }
    
        public class B extends A implements Serializable {
    
            private String nameB;
    
            public B(String nameA, String nameB) {
                   super(nameA);
               this.nameA += nameA;
                this.nameB = nameB;
            }
       //   private void writeObject(ObjectOutputStream oos) throws IOException {
        //        oos.defaultWriteObject();
            //    oos.writeObject(this.nameA);
            //    oos.writeObject(this.nameB);
    
          //  }
          //  private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
          //      ois.defaultReadObject();
            //    this.nameA = (String)ois.readObject();
             //   this.nameB = (String)ois.readObject();
          //  }
        }
    
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(arrayOutputStream);
    
            Solution solution = new Solution();
            B b = solution.new B("B2", "C33");
            System.out.println("nameA: " + b.nameA + ", nameB: " + b.nameB);
    
            oos.writeObject(b);
    
            ByteArrayInputStream arrayInputStream = new ByteArrayInputStream(arrayOutputStream.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(arrayInputStream);
    
            B b1 = (B)ois.readObject();
           System.out.println("nameA: " + b1.nameA + ", nameB: " + b1.nameB);
    
        }
    }
    
    

    Hi , thanks for this article. It really helped me understanding Custom Serialization in deep .
    i totally understood your article i believe but the purpose for which i opened this article in first place , is still unanswered.
    your article helped me understand the things which was not known to me like defaultReadObject etc.
    would you be more kind to help with above code .
    when i run this code: output is:

    nameA: AB2B2, nameB: C33
    nameA: A, nameB: C33

    and when i make change in above code like
    // String nameA = “A”; –commented this
    static String nameA=”A”; — uncommented this.

    output is:

    nameA: AB2B2, nameB: C33
    nameA: AB2B2, nameB: C33

    i am not able to understand why. Already invested 4-5 hours but still did not understand. Hope, you would help with this too.

    thanks you so much. have a good day.

    • Lokesh Gupta

      July 13, 2020

      During deserialization, after the blank instance is created, JVM first set it’s static fields and then invokes the readObject() API. Read more how the deserialization happen in Java?

  2. Rohit Singh

    June 18, 2019

    Hi

    This is valid scenario , if we can manipulate serialization based on business logic, then what is the difference between this and externalization.

    Please explain with example.

    Regards
    Rohit

  3. Daniel Pelisek

    May 19, 2019

    What if the account number (or any other field) is final? Is there a way to serialize it using own write/readObject methods? The default serialization would work, but this custom one not.

  4. SSS

    May 6, 2019

    no such method for write and set object at java class

  5. Amit

    December 15, 2018

    Good explanation.

    Consider explaining handling a case where a file was serialized with an older version of the class and then deserialized with a newer version of the class. The newer version has more fields than the older version. The readObject of the newer version will try to read additional fields which will throw IOException.

    So In order to keep track of the no of fields in the class, a field counter could be used. The field counter is then serialized by the defaultWriteObject() method. While defaultReadObject() reads the counter and then limit the reads to the counter value thus avoiding to read the fields added in the newer version. This way the newer version becomes compatible with the backward versions.

    My own implementation of User Class

    package com.amit.serialization;
    
    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 static final long serialVersionUID = 1L;
         
        private transient String firstName;
        private transient String lastName;
        private transient int accountNumber;
        private transient Date dateOpened;
        private transient String bankName;
        // fieldCount to keep track of the no of fields in the Object. For compatibility between versions.
        private int fieldCount = 0;
     
        public User(String firstName, String lastName, int accountNumber, Date dateOpened, String bankName) {
            super();
            this.firstName = firstName;
            this.lastName = lastName;
            this.accountNumber = accountNumber;
            this.dateOpened = dateOpened;
            this.bankName = bankName;
            this.fieldCount = 5;
        }
         
        public User() {
            super();
        }
     
        //Setters and Getters
     
        private void readObject(ObjectInputStream aInputStream) throws ClassNotFoundException, IOException
        {   
        	
        	aInputStream.defaultReadObject();
        	int currentCount = 0;
        	if (checkCount(currentCount)) {
        		return;
        	}
            firstName = aInputStream.readUTF();
            currentCount++;
            if (checkCount(currentCount)) {
        		return;
        	}
            lastName = aInputStream.readUTF();
            currentCount++;
            if (checkCount(currentCount)) {
        		return;
        	}
            accountNumber = aInputStream.readInt();
            currentCount++;
            if (checkCount(currentCount)) {
        		return;
        	}
            dateOpened = new Date(aInputStream.readLong());
            currentCount++;
            if (checkCount(currentCount)) {
        		return;
        	}
            bankName = aInputStream.readUTF();
        }
        
        private boolean checkCount(int currentCount) {
        	if (currentCount >= this.fieldCount) {
        		return true;
        	}
        	return false;
        }
     
        private void writeObject(ObjectOutputStream aOutputStream) throws IOException
        {
        	
        	aOutputStream.defaultWriteObject();
            aOutputStream.writeUTF(firstName);
            aOutputStream.writeUTF(lastName);
            aOutputStream.writeInt(accountNumber);
            aOutputStream.writeLong(dateOpened.getTime());
            aOutputStream.writeUTF(bankName);
        }
        
        
        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;
        }
        
        public final void setBankName( String aNewBankName) {
        	bankName = aNewBankName;
        }
        
        public final String getBankName() {
        	return bankName;
        }
    }
    

    At all the times, the new fields should be appended to the class and never added in-between.

    For more complex data structure like a linked list, this simple counter might not work. If may then require a more complicated tracking & reading mechanism.

    • Lokesh Gupta

      December 17, 2018

      Please refer to do’s and don’ts for correct serialization.

    • Ashu

      February 25, 2020

      If you add any new field in class. And then you’re de-serializing your persisted object of old class in to the class with newer fields. All the newer fields will get populate by its’ default datatype value.

  6. Priya

    October 5, 2017

    The serialization process is instance-independent, i.e. objects can be serialized on one platform and deserialized on another . It is important concept in java. Thanks a lot for sharing this.

  7. Navo

    August 31, 2017

    I have a concern.When we have readObject and writeObject () for custom serialization then what is the use of Externalizable.
    Please elaborate?

    • Shubhranshu

      June 10, 2018

      Java runtime uses reflection to determine how to marshal or unmarshal the objects of the Serializable class. During early versions of Java, reflection was very slow hence if you needed to serialize or deserialize large objects, then it used to cause a performance problem.
      To handle this Externalizable was introduced, which provided an option to remove the dependency on reflection by providing custom serialization and deserialisation logic. But after Java 1.3 there was a major improvement in the speed of reflection which filled the performance gap between Serializable and Externalizable.
      So to summarise, Externalizable is some thing which was useful in java 1.1 days, but there is no need for it anymore.

  8. Sharan De Silva

    July 11, 2017

    In User class accountNumber fields is `int`. Is it possible to change the datatype of accountNumber to `double` while serializing object.

    With the serialized stream can I able to get this class object,

    public class User implements Serializable {

    private static final long serialVersionUID = 7829136421241571165L;

    private String firstName;
    private String lastName;
    private Double accountNumber;
    private Date dateOpened;
    }

Comments are closed on this article!

Search Tutorials

Java Serialization Tutorial

  • Serialization – Serializable Interface
  • Serialization – SerialVersionUID
  • Serialization – Externalizable
  • Externalizable vs Serializable
  • Serialization – Java Object to XML
  • Serialization – Deserialization Process
  • Serialization – ReadObject and WriteObject
  • Serialization – Deep Cloning using In-memory Serialization

Java Tutorial

  • Java Introduction
  • Java Keywords
  • Java Flow Control
  • Java OOP
  • Java Inner Class
  • Java String
  • Java Enum
  • Java Collections
  • Java ArrayList
  • Java HashMap
  • Java Array
  • Java Sort
  • Java Clone
  • Java Date Time
  • Java Concurrency
  • Java Generics
  • Java Serialization
  • Java Input Output
  • Java New I/O
  • Java Exceptions
  • Java Annotations
  • Java Reflection
  • Java Garbage collection
  • Java JDBC
  • Java Security
  • Java Regex
  • Java Servlets
  • Java XML
  • Java Puzzles
  • Java Examples
  • Java Libraries
  • Java Resources
  • Java 14
  • Java 12
  • Java 11
  • Java 10
  • Java 9
  • Java 8
  • Java 7

Meta Links

  • About Me
  • Contact Us
  • Privacy policy
  • Advertise
  • Guest and Sponsored Posts

Recommended Reading

  • 10 Life Lessons
  • Secure Hash Algorithms
  • How Web Servers work?
  • How Java I/O Works Internally?
  • Best Way to Learn Java
  • Java Best Practices Guide
  • Microservices Tutorial
  • REST API Tutorial
  • How to Start New Blog

Copyright © 2020 · HowToDoInjava.com · All Rights Reserved. | Sitemap

  • Java 15 New Features
  • Sealed Classes and Interfaces
  • EdDSA (Ed25519 / Ed448)