javaserializationaspectjjavacserialversionuid

Serialization incompatibility between class generated by ajc and javac


Recently I found out that some classes compiled by Java (Java 8) and by ajc (v.1.9.2) are not serialization-compatible. By serialization-compatibility I mean that calculated default serialVersionUID are not same.

Example:

public class Markup implements Serializable {
    private final MyUnit unit;
    public Markup(MyUnit unit) { this.unit = unit; }

    public enum MyUnit { DOUBLE, STRING }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Path path = Paths.get("markup.bin");
        if (args.length == 0) {
            try (OutputStream fileOutput = Files.newOutputStream(path);
                 ObjectOutputStream objectOutput = new ObjectOutputStream(fileOutput))
            {
                objectOutput.writeObject(new Markup(MyUnit.STRING));
            }
        } else {
            try (InputStream fileInput = Files.newInputStream(path);
                 ObjectInputStream objectInput = new ObjectInputStream(fileInput))
            {
                System.out.println(objectInput.readObject());
            }
        }
    }

    static String switchType(MyUnit unit) {
        switch (unit) {
            case STRING: return "%";
            case DOUBLE: return "p";
            default: return "Undefined";
        }
    }
}

When I compile this class by ajc and run, and then compile this class by javac and run I get exception about incompatibility of serialization format:

Exception in thread "main" java.io.InvalidClassException: Markup; local class incompatible: stream classdesc serialVersionUID = -1905477862550005139, local class serialVersionUID = 793529206923536473

I also found out that's it's because of ajc switch code generator. It creates additional field in class private static int[] $SWITCH_TABLE$Markup$MyUnit

Fields generated by javac: enter image description here Fields generated by ajc: enter image description here

My questions are:

  1. Is it allowed by specification for java compiler to generate fields which are not defined in the class?
  2. Why ajc generates additional field? Some sort of performance optimization?
  3. Are there any way to make ajc to not generate additional field?
  4. What the reason why private static affects generation of serialVersionUID ?
  5. Are developers of aspectj aware of this behavior? If so, why they choose generate field anyway?
  6. Are there any guarantees how Java class will be serialized by JLS?
  7. How Javac-generated code works without this field?

Solution

  • 1. Is it allowed by specification for java compiler to generate fields which are not defined in the class?

    Yes, though they should be marked as synthetic. Synthetics include accessors for inner classes (although that implementation has changed recently), lambda expressions methods and, I think, default constructors.

    2. Why ajc generates additional field? Some sort of performance optimization?

    Looks like a bad performance optimisation. Perhaps it's something to do with adding aspects efficiently.

    3. Are there any way to make ajc to not generate additional field?

    No idea. You should expect synthetics to be generated regardless.

    4. What the reason why private static affects generation of serialVersionUID ?

    Java Serialization was created in "Internet time". For compatibility, we are left with whatever the first version happened to do. Moral: If you create anything in Internet time, do throw it away.

    5. Are developers of aspectj aware of this behavior? If so, why they choose generate field anyway?

    I would hope so. Compilers are expected to genera synthetics.

    6. Are there any guarantees how Java class will be serialized by JLS?

    There is a Java Serialization specification (though I don't really expect many people to read it).

    7. How Javac-generated code works without this field?

    It's relatively easy to see how you could write a switch-on-string without an array. Exact details of an ugly optimised version are going to be messy. You can see what actually happens with javap -private -c.

    Conclusion

    It is recommended to add a serialVersionUID if data is expected to be used between different versions of a class. OTOH, it's also recommended to not use Java Serialization.