Understand Java Serializable while actually running the application

Do you understand the meaning of Serializable?

When you create a web application in Java, you may create a JavaBeans class like the one below.

JavaBeans.java


import java.io.Serializable;

public class JavaBeans implements Serializable {

    private static final long serialVersionUID = 1L;

    private String aaa = null;

    public String getAaa() {
        return aaa;
    }

    public void setAaa(String aaa) {
        this.aaa = aaa;
    }
}

Do you understand what these ʻimplements Serializable and serialVersionUID = 1L; `mean? This article was written for people who say "I don't know!" People who say "I understand!" Do not need to read this article.

First from the concept

Outputting a Java instance as a byte array is called Serialize, and vice versa is called Deserialize. As you can see in the figure below, saving an instance in a file, memory, database, etc. is called Serialize.

image.png The figure is excerpted from this article

So declaring ʻimplements Serializable` means declaring" This instance can be saved to disk etc.! ".

I have a web application that I made a little personally, so I will experiment with it.

TestServlet.java


public class TestServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    //・ ・ ・
}

The above TestServlet extendsHttpServlet, HttpServlet extendsGenericServlet, and GenericServlet implementsSerializable, so TestServlet isSerializable.

image.png

Start the server (Tomcat) for this web application. There is nothing under the localhost folder of the deployment destination yet.

image.png

After this, open a browser or do something to make TestServlet work and stop the server. Then, a mysterious file called SESSIONS.ser will be created directly under the localhost folder of the deployment destination.

image.png

Start the server again. Then SESSIONS.ser disappears.

image.png

SESSIONS.ser is the serialized session information by Tomcat. Thanks to this, the session information is not lost even if the server is restarted, and the information before the restart can be restored (Deserialize) by accessing it after the restart. Conversely, for this specification, the objects stored in the session must implement Serializable. It seems convenient at first glance, but do you know what happens if the program is changed before and after the reboot?

Let's actually serialize / Deserialize

Create an application with the configuration shown below and actually serialize / Deserialize it.

image.png

First of all, the person who serializes. Create SampleBean.java and WriteObject.java to serialize it in any folder. WriteObject.java creates a SampleBean instance, sets two properties to 100 and serializes it. Create sample.ser by executing WriteObject. sample.ser is a Serialized version of the SampleBean instance.

image.png

SampleBean.java


import java.io.Serializable;

public class SampleBean implements Serializable {
    private static final long serialVersionUID = 1L;

    private double propertyD;
    private int propertyE;

    public double getPropertyD() {
        return propertyD;
    }

    public void setPropertyD(double propertyD) {
        this.propertyD = propertyD;
    }

    public int getPropertyE() {
        return propertyE;
    }

    public void setPropertyE(int propertyE) {
        this.propertyE = propertyE;
    }
}

WriteObject.java


import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

public class WriteObject {
    public static void main (String args[]) {
        SampleBean sample = new SampleBean();
        sample.setPropertyD(100);
        sample.setPropertyE(100);

        try {
            FileOutputStream fout = new FileOutputStream("sample.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fout);
            oos.writeObject(sample);
            oos.close();
            System.out.println("Done");
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }
}

Next is the person who deserializes. Create a class (JspTest.java) that reads the sample.ser generated above. The SampleBean class used in JspTest.java uses the same contents as the previous one by copying it to another folder.

JspTest.java


import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/JspTest")
public class JspTest extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        SampleBean sample = null;
        List<SampleBean> list = new ArrayList<>();
        try {
            FileInputStream fin = new FileInputStream("c:\\sample.ser");
            ObjectInputStream ois = new ObjectInputStream(fin);
            sample = (SampleBean) ois.readObject();
            ois.close();
            list.add(sample);
        } catch(Exception ex) {
            ex.printStackTrace();
        }

        request.setAttribute("samplelist", list);
        String path = "/WEB-INF/jsp/jsptest.jsp";
        RequestDispatcher disp = request.getRequestDispatcher(path);
        disp.forward(request, response);
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

Jsp to display it

jsptest.jsp


<%@ page language="java" contentType="text/html;charset=Windows-31J"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ page isELIgnored="false" %>

<html><body>
<table border="1">
  <thead>
    <tr>
      <th>Property D</th>
      <th>Property E</th>
    </tr>
  </thead>
  <tbody>
    <c:forEach items="${samplelist}" var="item">
      <tr>
        <td><c:out value="${item.propertyD}" /></td>
        <td><c:out value="${item.propertyE}" /></td>
      </tr>
    </c:forEach>
  <tbody>
</table>
</body></html>

Execution result (browser display) image.png

I was able to deserialize the serialized instance and display it in the browser. This is Serialize / Deserialize.

What is serialVersionUID?

Next is serialVersionUID. If you write implements Serializable in your class and don't declare serialVersionUID, you'll get a warning that The serializable class XXXX does not declare a static final serialVersionUID field of type long. serialVersionUID is the version of the instance. I don't think it's very clear, so I'll do a little experiment to understand it more concretely. Let's make the SerialVersionUID of SampleBean.java used when Serializing earlier and the serialVersionUID of SampleBean.java used when deserializing it inconsistent. Change SampleBean.java (the one that Deserializes) used by JspTest.java to serialVersionUID = 2L;. The serialized SampleBean.java remains serialVersionUID = 1L;. After making the change, if you update the browser with F5, the following error will be output.

error contents


java.io.InvalidClassException: SampleBean; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
	at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:715)
	at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1997)
	at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1866)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2159)
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1685)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:499)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:457)
	at JspTest.doGet(JspTest.java:27)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:690)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.base/java.lang.Thread.run(Thread.java:832)

I get the error ʻInvalidClassException` and know that the cause is a mismatch in the serialVersionUID of the SampleBean. On the contrary, what happens if the serialVersionUID has the same value but the contents are different? Do not change the SampleBean of the serialize person, and only the Deserialize person will try x100 in the getter as shown below (in getPropertyE ()).

SampleBean.java (change only on Deserialize side)


import java.io.Serializable;

public class SampleBean implements Serializable {
    private static final long serialVersionUID = 1L;

    private double propertyD;
    private int propertyE;

    public double getPropertyD() {
        return propertyD;
    }

    public void setPropertyD(double propertyD) {
        this.propertyD = propertyD;
    }

    public int getPropertyE() {
        return propertyE * 100;    //Try x100 in the getter
    }

    public void setPropertyE(int propertyE) {
        this.propertyE = propertyE;
    }
}

Execution result (browser display) image.png

You can see that no error has occurred and the value x100 is displayed. Although it ended normally, Is this behavior really what you expected? </ font> Have you ever considered the behavior when the contents of the program are changed between Serialize and Deserialize? What Deserialize restores is the value of the instance variable. At the time of serialization, double type 100 is set for propertyD and int type 100 is set for propertyE, so Deserialize restores that value. I don't care what I'm doing in the setter / getter.

image.png

So, for example, even if you check in the setter that an error will occur if the argument value is 10 or more, the value that ignores it will be restored.

SampleBean.java


import java.io.Serializable;

public class SampleBean implements Serializable {
    private static final long serialVersionUID = 1L;

    private double propertyD;
    private int propertyE;

    public double getPropertyD() {
        return propertyD;
    }

    public void setPropertyD(double propertyD) {
        //Error if the argument value is 10 or more
        if (propertyD > 10) {
            throw new IllegalArgumentException();
        }
        this.propertyD = propertyD;
    }

    public int getPropertyE() {
        return propertyE;
    }

    public void setPropertyE(int propertyE) {
        this.propertyE = propertyE;
    }
}

Execution result (browser display) image.png

If it is 10 or more in setter, it can be displayed without any error even though it is checked as error. Earlier in this article, I mentioned that session information is Serialized / Deserialized. The web server serializes session information in the following cases.

--When the server is stopped --When the area (memory) for storing the session is full

Deserialize at the opposite timing. Even if the program is changed between the time it is serialized and the time it is deserialized, the change will be ignored and the program will be deserialized. If that bothers you, you need to change the serialVersionUID.

The Java specifications for Serialize / Deserialize are summarized below. 5.6 Type changes affecting serialization What I would like you to be especially aware of is "5.6.2 compatible changes", and even if the Java specifications are compatible (= Deserialize possible), they are compatible with the system you are actually using. Whether or not there is another story. Is it really okay to deserialize if it is compatible as a Java specification? If something strange happens if you do not operate the screen again, it may be safer to change the serialVersionUID and generate an error.

How to change serialVersionUID

You can change it by yourself, but you can also let the IDE generate it. In Eclipse, the procedure is as follows. (If you are using IntelliJ IDEA, here) Remove private static final long serialVersionUID = 1L; from the class. Then, a warning will appear in the class, so place the cursor there (see the figure below).

image.png

A serialVersionUID of private static final long serialVersionUID = 2779728212248637579L; was generated. This value will change as the structure of the class changes. The structure of a class is adding an instance variable, renaming a variable, adding a method, and so on.

Exclude Serialize / Deserialize with transient

By adding the transient modifier to the instance variable, it can be excluded from Serialize / Deserialize. As a test, add the transient modifier to propertyE as shown below.

SampleBean.java


import java.io.Serializable;

public class SampleBean implements Serializable {

    private static final long serialVersionUID = 4798282072280430232L;
    private double propertyD;
    private transient int propertyE;  //Grant transient

    public double getPropertyD() {
        return propertyD;
    }

    public void setPropertyD(double propertyD) {
        this.propertyD = propertyD;
    }

    public int getPropertyE() {
        return propertyE;
    }

    public void setPropertyE(int propertyE) {
        this.propertyE = propertyE;
    }
}

Set to 100 when serializing.

WriteObject.java


import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

public class WriteObject {
    public static void main (String args[]) {
        SampleBean sample = new SampleBean();
        sample.setPropertyD(100);
        sample.setPropertyE(100);

        try {
            FileOutputStream fout = new FileOutputStream("sample.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fout);
            oos.writeObject(sample);
            oos.close();
            System.out.println("Done");
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }
}

The result of loading the sample.ser generated here and displaying it in the browser is as follows.

Execution result (browser display) image.png

By adding transient to propertyE, you can see that the value 100 set during Serialize is not set when Deserialize. 0 is displayed because the default value of int is 0. By the way, adding transient also changes the value of serialVersionUID generated by the IDE.

in conclusion

It's roughly like this. There are some parts that I don't understand well, but I felt that there weren't many people around me who wrote ʻimplements Serializable or serialVersionUID = 1L after understanding this area properly, so I wrote it. I tried it. I hope that as many people as possible after reading this article will understand the meaning of Serialize and write ʻimplements Serializable or serialVersionUID = 1L.

that's all.

reference

official

-5.6 Type changes affecting serialization -System Architecture --Version control of serializable objects

Serialize: Serialize and Deserialize: Deserialize

-Serialize (serialize)

Serialize and session

-[3.1.14 How to create a Web application when using the session recovery function](https://software.fujitsu.com/jp/manual/manualfiles/m140003/b1ws1025/04z200/b1025-00-03-01-14 .html) -Object saved in session is not serializable -Object registered in session must be Serializable -[[JAVA] NullPointerException and Cannot get property ‘xxxxxx’ on null object] at failover or restart (https://huge.mints.ne.jp/11/2013/it_technique/groovy/grails/512/)

serialVersionUID

-Understanding serialVersionUID -How to automatically generate the serial version UID of Eclipse Java -Generate serialVersionUID in IntelliJ IDEA

Tomcat session management

-Tomcat session persistence -(Working) tomcat restart part 44 session manager

Recommended Posts

Understand Java Serializable while actually running the application
[java8] To understand the Stream API
Java reference to understand in the figure
java serializable
[Java] Understand the difference between List and Set
Check the options set for the running Java process
[Java / PostgreSQL] Connect the WEB application to the database
Java 9+ application status
Understand java constructor
Trace the SQL executed with the application Insights java agent
[Tutorial] Download Eclipse → Run the application with Java (Pleiades)
Compile with Java 6 and test with Java 11 while running Maven on Java 8
[For beginners] Quickly understand the basics of Java 8 Lambda
Java classes and instances to understand in the figure