0

I have an Issue deserialising a Java Instance with 3 fields.
2 of the fields are being deserialised as I would expect.

The 3rd is being initialised as defined in the Class.
I would expect it to be deserialised too.

I am using openJDK 17.

Below is some example code.

  1. Run the Programme as is
  2. WAIT a few seconds
  3. comment in & out as indicated (3 TODO's) & run again

First time through I get this:

11:09:02 SerialTest then.: 11:09:02.870987 msThen.: 1680685742677 msNow.: 1680685742988
11:09:04 SerialTest then.: 11:09:02.870987 msThen.: 1680685742677 msNow.: 1680685744990
11:09:06 SerialTest then.: 11:09:02.870987 msThen.: 1680685742677 msNow.: 1680685746992
11:09:08 SerialTest then.: 11:09:02.870987 msThen.: 1680685742677 msNow.: 1680685748993
11:09:10 SerialTest then.: 11:09:02.870987 msThen.: 1680685742677 msNow.: 1680685750994

Second time, this:

11:09:40 Why this?! then.: 11:09:02.870987 msThen.: 1680685742677 msNow.: 1680685780744
11:09:42 Why this?! then.: 11:09:02.870987 msThen.: 1680685742677 msNow.: 1680685782747
11:09:44 Why this?! then.: 11:09:02.870987 msThen.: 1680685742677 msNow.: 1680685784749
11:09:46 Why this?! then.: 11:09:02.870987 msThen.: 1680685742677 msNow.: 1680685786751
11:09:48 Why this?! then.: 11:09:02.870987 msThen.: 1680685742677 msNow.: 1680685788751

The "then" Millis & LocalTime are being deserialised as I had expected & remain constant.
Can anyone help me to understand why className is not being deserialised?

import java.io.*;
import java.nio.file.*;
import java.time.*;
import java.time.temporal.*;

public final class SerialTest implements Serializable {

    private static final long    serialVersionUID = -5422947008060509280L;
    private static final Path    PATH             = Path.of("SerialTest.ser");

    public         final long    millisThen       = System.currentTimeMillis();
    public         final String  dateTimeThen     = LocalTime.now().toString();

    public         final String  className        =  SerialTest.class.getName(); // TODO Comment out
//  public         final String  className        = "Why this?!";                // TODO Comment in

    public static void main(final String[] args) throws Exception {

        Files.write(PATH, new SerialTest().serialize()); // TODO Comment out

        deserialize();    sleep(Duration.ofSeconds(2));
        deserialize();    sleep(Duration.ofSeconds(2));
        deserialize();    sleep(Duration.ofSeconds(2));
        deserialize();    sleep(Duration.ofSeconds(2));
        deserialize();    sleep(Duration.ofSeconds(2));
    }

    private static void sleep(final Duration duration) {
        try {Thread.sleep(duration.toMillis());} catch (final Exception e) {}
    }

    private static void deserialize() throws Exception {

        final InputStream ist = new  ByteArrayInputStream(Files.readAllBytes(PATH));

        final SerialTest  ser = (SerialTest) new ObjectInputStream(ist).readObject();

        System.out.print  (""           + LocalTime.now().truncatedTo(ChronoUnit.SECONDS));
        System.out.print  (" "          + ser.className);
        System.out.print  (" then.: "   + ser.dateTimeThen);
        System.out.print  (" msThen.: " + ser.millisThen);
        System.out.println(" msNow.: "  + System.currentTimeMillis());
    }

    public byte[] serialize() throws IOException {

        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        final    ObjectOutputStream oos  = new    ObjectOutputStream(baos);

        try {
            oos.writeObject(this);
        }
        finally {
            oos.close();
        }
        return baos.toByteArray();
    }
}
Dave The Dane
  • 650
  • 1
  • 7
  • 18
  • I'd say the class name (or any other string) not being deserialized would be more expected since the field is declared final. Without that it would work. So the question would rather be: what happens to `dateTimeThen`? – Thomas Apr 05 '23 at 10:04
  • In the 1st execution, className contains the Class name, & is serialised. In the 2nd execution, it is initialised to "Why this?!" but I expect it to be overwritten by the serialised value from the 1st execution, just as the other 2 fields. All 3 fields are declared final, so no difference there. If we consider the 2 final Strings dateTimeThen & className, why is one being deserialised & the other not? – Dave The Dane Apr 05 '23 at 10:05
  • That is a very plausible explanation... – Dave The Dane Apr 05 '23 at 10:27
  • ...but scary!!! – Dave The Dane Apr 05 '23 at 10:28
  • yes, I'd already checked that. Many, many thanks for your feedback. Now I wonder if its a bug or a "works as designed"? :) – Dave The Dane Apr 05 '23 at 10:39
  • Also, you can fool it by coding the initialiser as... – Dave The Dane Apr 05 '23 at 10:47
  • "public final String className = new String("Why this?!".getBytes());" – Dave The Dane Apr 05 '23 at 10:47
  • "Now I wonder if its a bug or a "works as designed"?" - In general I'd try to avoid Java's serialization and go for more specialized implementations/approaches/formats like json, avro, protobuff etc. – Thomas Apr 05 '23 at 11:15
  • @Thomas In the end we had to abandon Serialisation as it was not possible to get the Payload Class in the same package on the various (Point & Click) Client Apps we have & that is a prerequisite of Serialisation, so maybe we'll take a look at one of the solutions you suggested... – Dave The Dane Apr 05 '23 at 12:09
  • 2
    `new ByteArrayInputStream(Files.readAllBytes(PATH))` is not how programs should read files. Use `new BufferedInputStream(Files.newInputStream(PATH))` instead. I know this is just a simple test, but it’s a good idea to get in the habit of reading files in a way that can scale up without any risk of maxing out program memory. – VGR Apr 05 '23 at 13:04
  • 1
    `final String className = "Why this?!"` is a compile-time constant, regardless of whether this is a `static` or instance field or even a local variable. See [this answer](https://stackoverflow.com/a/70335372/2711488), for example. So this “works as designed”. And in addition to [@VGR’s comment](https://stackoverflow.com/questions/75937755/java-deserialisation-anomaly#comment133942210_75937755), you should also use [try-with-resources](https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html). See also https://stackoverflow.com/q/39386014/2711488 – Holger Apr 05 '23 at 17:32

0 Answers0