Search code examples
javatypesserializabletransient

java transient variable type must be known for serialization?


I noticed that in Java, you can use a class which has members of types that are unavailable to you. For example, say you have a precompiled .class file for the following Person class; but not for Address, which it uses:

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String sex;
    private String name;
    private transient Address address;

    public Person(String sex, String name) {
        this.sex = sex; this.name = name; 
    }
    public void setAddress(Address address) {
        this.address = address
    }
}

You can instantiate this class with Person person = Person("Male", "Andrew"); even if the type Address is not available to your Java project.

Now the problem is, if you want to serialize a Person object, you cannot do this unless the Address type is available (i.e. can be loaded by the class loader), even though the Address field is transient.

For some reason Java needs to know the type Address even though it will not need to serialize it...

Any help on how I could serialize Person anyway without having to redefine a Person class without an address? And any explanation of why Java needs to know the type of address although it will not need to serialize it?

Many thanks.


Solution

  • I am operating under the assumption that you have a .class file for Person, but no .class file for Address. (Perhaps Person is a dependency from a different project?)

    The reason you cannot serialize Person (even though the Address field is transient) is that the serialization API uses the Java reflection API. Although the JVM will load a class without loading all of its dependencies, the reflection API is not as forgiving.

    The first time the reflection API is used to retrieve field information for a particular class, it retrieves and caches information for all fields in the class. To do this, it has to resolve the types for every field in the class, and thus will attempt to load the class files for every field. (You can see this in the Java source code: ObjectOutputStream uses ObjectStreamClass, which calls Class.getDeclaredField, which calls a private method privateGetDeclaredFields, which resolves and caches all the field definitions for the class.)

    As a workaround, if your code never actually uses any objects of type Address, you can simply create an empty Address class in the correct package, compile it, and add it to your class path:

    public class Address { }
    

    For those who think it isn't possible to use Person at runtime without an Address class definition, the following three classes demonstrate what the OP is talking about:

    Person.java

    import java.io.Serializable;
    public class Person implements Serializable {
        private String name;
        private transient Address address;
        public Person(String name)  { this.name = name; }
        public Address getAddress() { return address; }
        public String getName()     { return name; }
    }
    

    Address.java

    public class Address {
        private String address;
        public String getAddress()  { return address; }
    }
    

    Test.java

    import java.io.FileOutputStream;
    import java.io.ObjectOuputStream;
    public class Test {
        public static void main(String...args) throws Exception {
            Person person = new Person("John Doe");
            System.out.println("Person successfully instantiated with name " + person.getName());
    
            // now attempt to serialize
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.out"));
            out.writeObject(person); // NoClassDefFoundError thrown here if Address.class doesn't exist
            out.close();
        }
    }
    

    Now compile Test.java, delete Address.class, and run Test:

    $ javac Test.java
    $ rm Address.class
    $ java Test
    Person successfully instantiated with name John Doe
    Exception in thread "main" java.lang.NoClassDefFoundError: LAddress;
            at java.lang.Class.getDeclaredFields0(Native Method)
            at java.lang.Class.privateGetDeclaredFields(Class.java:2308)
            at java.lang.Class.getDeclaredField(Class.java:1897)
            at java.io.ObjectStreamClass.getDeclaredSUID(ObjectStreamClass.java:1624)
            at java.io.ObjectStreamClass.access$700(ObjectStreamClass.java:69)
            at java.io.ObjectStreamClass$2.run(ObjectStreamClass.java:442)
            at java.security.AccessController.doPrivileged(Native Method)
            at java.io.ObjectStreamClass.<init>(ObjectStreamClass.java:430)
            at java.io.ObjectStreamClass.lookup(ObjectStreamClass.java:327)
            at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1130)
            at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:346)
            at Test.main(Test.java:10)
    Caused by: java.lang.ClassNotFoundException: Address
            at java.net.URLClassLoader$1.run(URLClassLoader.java:217)
            at java.security.AccessController.doPrivileged(Native Method)
            at java.net.URLClassLoader.findClass(URLClassLoader.java:205)
            at java.lang.ClassLoader.loadClass(ClassLoader.java:321)
            at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:294)
            at java.lang.ClassLoader.loadClass(ClassLoader.java:266)
            ... 12 more