Java IO Tutorial

Java Serialization and Deserialization Examples

In Java, Serialization means converting Java objects into a byte stream; Deserialization means converting the serialized object’s byte stream back to the original Java object.

Table of contents.

1. Hello World Java Serialization

In Java, we have to implement the Serializable interface to supports serialization and deserialization.

1.1 A Java object.

Person.java

package com.mkyong.io.object;

import java.io.Serializable;
import java.math.BigDecimal;

public class Person implements Serializable {

    private String name;
    private int age;
    private BigDecimal salary;

    // getters setters constructor
}    

1.2 A complete Java Serialization and Deserialization example for a Java object Person.java.

HelloSerialization.java

package com.mkyong.io.object;

import java.io.*;
import java.math.BigDecimal;

public class HelloSerialization {

    public static void main(String[] args) {

        Person person = new Person("mkyong", 40, new BigDecimal(900));

        byte[] bytes = convertObjectToBytes(person);

        Person p = (Person) convertBytesToObject(bytes);

        System.out.println(p);
    }

    // Convert object to byte[]
    public static byte[] convertObjectToBytes(Object obj) {
        ByteArrayOutputStream boas = new ByteArrayOutputStream();
        try (ObjectOutputStream ois = new ObjectOutputStream(boas)) {
            ois.writeObject(obj);
            return boas.toByteArray();
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
        throw new RuntimeException();
    }

    // Convert byte[] to object
    public static Object convertBytesToObject(byte[] bytes) {
        InputStream is = new ByteArrayInputStream(bytes);
        try (ObjectInputStream ois = new ObjectInputStream(is)) {
            return ois.readObject();
        } catch (IOException | ClassNotFoundException ioe) {
            ioe.printStackTrace();
        }
        throw new RuntimeException();
    }

}

Output

Terminal

  Person{name='mkyong', age=40, salary=900}

2. java.io.NotSerializableException

If we serialize an object which didn’t implement the Serializable interface, Java throws java.io.NotSerializableException.

Person.java

public class Person {

    private String name;
    private int age;
    private BigDecimal salary;

    // getters setters constructor
}    
HelloSerialization.java

  Person person = new Person("mkyong", 40, new BigDecimal(900));

  byte[] bytes = convertObjectToBytes(person);

Output

Terminal

java.io.NotSerializableException: com.mkyong.io.object.Person
  	at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1197)
  	at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:354)
  	at com.mkyong.io.object.HelloSerialization.convertObjectToBytes(HelloSerialization.java:22)
  	at com.mkyong.io.object.HelloSerialization.main(HelloSerialization.java:12)
  Exception in thread "main" java.lang.RuntimeException
  	at com.mkyong.io.object.HelloSerialization.convertObjectToBytes(HelloSerialization.java:27)
  	at com.mkyong.io.object.HelloSerialization.main(HelloSerialization.java:12)  

3. What is serialVersionUID?

If the serialVersionUID is missing, the JVM will create it automatically. The serialVersionUID is something like version number; in short, if we save an object with 1L, we need to provide the same 1L to read the object, else hits an incompatible error.

Person.java

public class Person implements Serializable {

    private static final long serialVersionUID = 1L;
    //...
}

For example, we saved an object with serialVersionUID = 1L into a file name person.obj. Later we add or delete some fields from the object, and update serialVersionUID to 2L. Now, read the person.obj file and try to convert it back to the modified object; since both serialVersionUID is different, we will hit the following incompatible error:

Terminal

Exception in thread "main" java.io.InvalidClassException: com.mkyong.io.object.Person;

  local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2

	at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:689)
	at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1903)
	at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1772)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2060)
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1594)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:430)
	at com.mkyong.io.object.ObjectUtils.readObject(ObjectUtils.java:25)
	at com.mkyong.io.object.ObjectUtils.main(ObjectUtils.java:38)  

4. What is transient?

During serialization, JVM ignores all transient fields. If we need to exclude specific object fields during serialization, mark them as transient.

Person.java

public class Person implements Serializable {

    private String name;
    private int age;

    //exclude this field
    private transient BigDecimal salary;

    // getters setters constructor
}    

  Person person = new Person("mkyong", 40, new BigDecimal(900));

  byte[] bytes = convertObjectToBytes(person);

  Person p = (Person) convertBytesToObject(bytes);

  System.out.println(p);

Output

Terminal

Person{name='mkyong', age=40, salary=null}

5. Serialize Object to File

This example serializes a Java object to a file and deserializes the file back to the original object.

HelloSerializationFile.java

package com.mkyong.io.object;

import java.io.*;
import java.math.BigDecimal;

public class HelloSerializationFile {

  public static void main(String[] args) throws IOException, ClassNotFoundException {

      Person person = new Person("mkyong", 50, new BigDecimal(1000));

      File file = new File("person.anything");

      writeObjectToFile(person, file);

      Person p = readObjectFromFile(file);

      System.out.println(p);

  }

  // Serialization
  // Save object into a file.
  public static void writeObjectToFile(Person obj, File file) throws IOException {
      try (FileOutputStream fos = new FileOutputStream(file);
           ObjectOutputStream oos = new ObjectOutputStream(fos)) {
          oos.writeObject(obj);
          oos.flush();
      }
  }

  // Deserialization
  // Get object from a file.
  public static Person readObjectFromFile(File file) throws IOException, ClassNotFoundException {
      Person result = null;
      try (FileInputStream fis = new FileInputStream(file);
           ObjectInputStream ois = new ObjectInputStream(fis)) {
          result = (Person) ois.readObject();
      }
      return result;
  }

}

6. Why need Serialization in Java?

Some use cases for Java serialization:

  1. A data-interchange format between sockets, applications, clients, and servers. It likes JSON or XML but in byte format.
  2. The serialization is the foundation of several critical Java’s technologies, including Java Remote Method Invocation (Java RMI), Common Object Request Broker Architecture (CORBA), and distributed computing such as Java Naming and Directory Interface (JNDI), Java
    Management Extensions (JMX) and Java Messaging (JMS).
  3. For gaming as lightweight persistence, we can serialize the current game’s state on disk and restore it later. The file is in byte format, and we can’t easily modify the sensitive game’s data like weapons or money.
  4. Saving an object graph disk for further analysis, like UML class diagram.
  5. Cluster of servers and restoring a session. For example, a cluster of servers (Server A and Server B) needs to synchronize the session if one of the servers is shut down or restart. When Server A is shutting down, it saves the objects as an attribute of the HttpSession and sends it over the network for Server B, and Server B can restore the objects from the HttpSession. Read this post

P.S For data-interchange, consider human-readable data-interchange format like JSON, XML or Google protocol buffers.

7. Deserialization of Untrusted Data

However, deserialize from untrusted byte streams is dangerous and can result in the following Java vulnerabilities:

  • Turtles All The Way Down – Target nested containers such as lists within a list.
  • SerialDOS or Denial of Service (DoS)
  • Pufferfish
  • Remote code execution (RCE)

7.1 Deserialization and StackOverflow

Deserialize the below byte stream, and it throws StackOverflowError.

StackOverflowExample.java

package com.mkyong.io.object.attack;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class StackOverflowExample {

    public static void main(String[] args) {

        System.out.println(bomb().length);

        deserialize(bomb());  // throws StackOverflow

        System.out.println("Done");
    }

    static byte[] bomb() {
        HashMap map = new HashMap();
        List list = new ArrayList();
        map.put(list, "");
        list.add(list);
        return serialize(map);
    }

    public static byte[] serialize(Object o) {
        ByteArrayOutputStream ba = new ByteArrayOutputStream();
        try {
            new ObjectOutputStream(ba).writeObject(o);
        } catch (IOException e) {
            throw new IllegalArgumentException(e);
        }
        return ba.toByteArray();
    }


    public static Object deserialize(byte[] bytes) {
        try {
            return new ObjectInputStream(
                    new ByteArrayInputStream(bytes)).readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new IllegalArgumentException(e);
        }
    }

}

Output

Terminal

Exception in thread "main" java.lang.StackOverflowError
  at java.base/java.util.ArrayList.hashCode(ArrayList.java:582)
  at java.base/java.util.ArrayList.hashCodeRange(ArrayList.java:595)
  //...

7.2 Deserialization and Denial-of-service attack (DoS attack)

Deserializing the below byte stream will keep the process running, hanging there, and slowly slowing down the system, a classic DoS attack.

DosExample.java

package com.mkyong.io.object.attack;

import java.io.*;
import java.util.HashSet;
import java.util.Set;

public class DosExample {

  public static void main(String[] args) throws Exception {
      System.out.println(bomb().length);

      deserialize(bomb());  // Dos here

      System.out.println("Done");
  }

  static byte[] bomb() {
      Set<Object> root = new HashSet<>();
      Set<Object> s1 = root;
      Set<Object> s2 = new HashSet<>();
      for (int i = 0; i < 100; i++) {
          Set<Object> t1 = new HashSet<>();
          Set<Object> t2 = new HashSet<>();
          t1.add("test-" + i); // make it not equal to t2

          s1.add(t1); // root also add set
          s1.add(t2);

          s2.add(t1);
          s2.add(t2);

          s1 = t1;    // reference to t1, so that `root` can add new set from the last t1
          s2 = t2;
      }
      return serialize(root);
  }

  public static byte[] serialize(Object o) {
      ByteArrayOutputStream ba = new ByteArrayOutputStream();
      try {
          new ObjectOutputStream(ba).writeObject(o);
      } catch (IOException e) {
          throw new IllegalArgumentException(e);
      }
      return ba.toByteArray();
  }


  public static Object deserialize(byte[] bytes) {
      try {
          return new ObjectInputStream(
                  new ByteArrayInputStream(bytes)).readObject();
      } catch (IOException | ClassNotFoundException e) {
          throw new IllegalArgumentException(e);
      }
  }

}

Output

Terminal

  // keep the process running, hanging there
  // and slowly slowing down the system, a classic DoS attack.

8. Java 9 Deserialization filters

Java 9, JEP 290 introduced deserialization filters ObjectInputFilter to filter the incoming serialization data.

8.1 Deserialization Filter for StackOverflow

Refactor the above StackOverflowExample.java example with a deserialization filter of maxdepth=2. If we rerun the program, the deserialization process will stop and return filter status: REJECTED.

StackOverflowExample.java

package com.mkyong.io.object.attack;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class StackOverflowExample {

  public static void main(String[] args) {
      System.out.println(bomb().length);

      //deserialize(bomb());  // throws StackOverflow

      ObjectInputFilter filter =
              ObjectInputFilter.Config.createFilter(
                      "maxdepth=2;java.base/*;!*");

      // java.io.InvalidClassException: filter status: REJECTED
      deserializeFilter(bomb(), filter);  

      System.out.println("Done");
  }

  static byte[] bomb() {
      HashMap map = new HashMap();
      List list = new ArrayList();
      map.put(list, "");
      list.add(list);
      return serialize(map);
  }

  public static byte[] serialize(Object o) {
      ByteArrayOutputStream ba = new ByteArrayOutputStream();
      try {
          new ObjectOutputStream(ba).writeObject(o);
      } catch (IOException e) {
          throw new IllegalArgumentException(e);
      }
      return ba.toByteArray();
  }


  public static Object deserializeFilter(byte[] bytes, ObjectInputFilter filter) {

      try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
           ObjectInputStream ois = new ObjectInputStream(bais)) {

          // add filter before readObject
          ois.setObjectInputFilter(filter);

          return ois.readObject();
      } catch (IOException | ClassNotFoundException e) {
          throw new IllegalArgumentException(e);
      }

  }

}

Output

Terminal

Exception in thread "main" java.lang.IllegalArgumentException: java.io.InvalidClassException: filter status: REJECTED
  at com.mkyong.io.object.attack.StackOverflowExample.deserializeFilter(StackOverflowExample.java:70)
  at com.mkyong.io.object.attack.StackOverflowExample.main(StackOverflowExample.java:27)
  Caused by: java.io.InvalidClassException: filter status: REJECTED

8.2 Deserialization Filter for DoS attack

Refactor the above DosExample.java example with maxdepth deserialization filter to stop the DoS attack.

DosExample.java

package com.mkyong.io.object.attack;

import java.io.*;
import java.util.HashSet;
import java.util.Set;

public class DosExample {

  public static void main(String[] args) throws Exception {
      System.out.println(bomb().length);

      //deserialize(bomb());  // Dos here

      ObjectInputFilter filter =
              ObjectInputFilter.Config.createFilter(
                      "maxdepth=10;java.base/*;!*");

      deserializeFilter(bomb(), filter);  // Dos here

      System.out.println("Done");
  }

  static byte[] bomb() {
      Set<Object> root = new HashSet<>();
      Set<Object> s1 = root;
      Set<Object> s2 = new HashSet<>();
      for (int i = 0; i < 100; i++) {
          Set<Object> t1 = new HashSet<>();
          Set<Object> t2 = new HashSet<>();
          t1.add("test-" + i); // make it not equal to t2

          s1.add(t1); // root also add set
          s1.add(t2);

          s2.add(t1);
          s2.add(t2);

          s1 = t1;    // reference to t1, so that `root` can add new set from the last t1
          s2 = t2;
      }
      return serialize(root);
  }

  public static byte[] serialize(Object o) {
      ByteArrayOutputStream ba = new ByteArrayOutputStream();
      try {
          new ObjectOutputStream(ba).writeObject(o);
      } catch (IOException e) {
          throw new IllegalArgumentException(e);
      }
      return ba.toByteArray();
  }

  public static Object deserializeFilter(byte[] bytes, ObjectInputFilter filter) {

      try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
           ObjectInputStream ois = new ObjectInputStream(bais)) {

          // add filter before readObject
          ois.setObjectInputFilter(filter);

          return ois.readObject();
      } catch (IOException | ClassNotFoundException e) {
          throw new IllegalArgumentException(e);
      }

  }

}

If we rerun the program, the deserialization process will stop and returns filter status: REJECTED.

Terminal

Exception in thread "main" java.lang.IllegalArgumentException:
  java.io.InvalidClassException: filter status: REJECTED
  at com.mkyong.io.object.attack.DosExample.deserializeFilter(DosExample.java:74)
  at com.mkyong.io.object.attack.DosExample.main(DosExample.java:19)

Note

Read the official guide on Serialization Filtering.

9. Java 17 Context-Specific Deserialization Filters

Java 17 JEP 415 introduced a filter factory, which allows choosing different deserialization filters dynamically or context-specific, refer to the Java 17 filter factory examples.

Download Source Code

$ git clone https://github.com/mkyong/core-java.git

$ cd java-io/com/mkyong/io/object

References

About Author

author image
Founder of Mkyong.com, love Java and open source stuff. Follow him on Twitter. If you like my tutorials, consider make a donation to these charities.

Comments

Subscribe
Notify of
0 Comments
Inline Feedbacks
View all comments